Compare commits
10 Commits
09102b934b
...
6a57e689ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a57e689ac | ||
|
|
88e33be91b | ||
|
|
cd745f0eda | ||
|
|
fc3192cd1e | ||
|
|
c081aa868f | ||
|
|
cf20ed827e | ||
|
|
7ae69ea1ff | ||
|
|
01eaebeb71 | ||
|
|
5534b169d6 | ||
|
|
c7c679c378 |
@@ -23,20 +23,46 @@ FetchContent_Declare(
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(flecs doctest)
|
||||
# GLFW (windowing for node-editor tool)
|
||||
FetchContent_Declare(
|
||||
glfw
|
||||
GIT_REPOSITORY https://github.com/glfw/glfw.git
|
||||
GIT_TAG 3.4
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
|
||||
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# Dear ImGui
|
||||
FetchContent_Declare(
|
||||
imgui
|
||||
GIT_REPOSITORY https://github.com/ocornut/imgui.git
|
||||
GIT_TAG v1.91.6
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
# imnodes
|
||||
FetchContent_Declare(
|
||||
imnodes
|
||||
GIT_REPOSITORY https://github.com/Nelarius/imnodes.git
|
||||
GIT_TAG v0.5
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(flecs doctest glfw imgui imnodes)
|
||||
|
||||
# ── Core library ──────────────────────────────────────────────────────────────
|
||||
|
||||
# Only compile sources needed for the core library
|
||||
set(SOURCES
|
||||
src/Components/Config/WorldConfig.cpp
|
||||
src/Components/Support.cpp
|
||||
src/Components/WorldGraph.cpp
|
||||
src/Core/WorldInstance.cpp
|
||||
)
|
||||
|
||||
add_library(factory-hole-core ${SOURCES})
|
||||
|
||||
# Main executable
|
||||
add_executable(factory-hole-app src/main.cpp)
|
||||
target_link_libraries(factory-hole-app PRIVATE factory-hole-core)
|
||||
|
||||
target_include_directories(factory-hole-core PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
)
|
||||
@@ -46,7 +72,13 @@ target_link_libraries(factory-hole-core PUBLIC
|
||||
doctest::doctest
|
||||
)
|
||||
|
||||
# Tests
|
||||
# ── Main executable ───────────────────────────────────────────────────────────
|
||||
|
||||
add_executable(factory-hole-app src/main.cpp)
|
||||
target_link_libraries(factory-hole-app PRIVATE factory-hole-core)
|
||||
|
||||
# ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
enable_testing()
|
||||
|
||||
file(GLOB_RECURSE TEST_SOURCES tests/*.cpp)
|
||||
@@ -63,3 +95,7 @@ target_link_libraries(factory-hole-tests PRIVATE
|
||||
)
|
||||
|
||||
add_test(NAME factory-hole-tests COMMAND factory-hole-tests)
|
||||
|
||||
# ── Node editor tool ──────────────────────────────────────────────────────────
|
||||
|
||||
add_subdirectory(tools/node-editor)
|
||||
|
||||
1
graph.json
Normal file
1
graph.json
Normal file
File diff suppressed because one or more lines are too long
8
imgui.ini
Normal file
8
imgui.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
Size=400,400
|
||||
|
||||
[Window][##canvas]
|
||||
Pos=0,19
|
||||
Size=1280,701
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include "Types/Item.hpp"
|
||||
#include "Util/SharedBuffer.h"
|
||||
|
||||
struct Chute
|
||||
{
|
||||
struct ChuteLink
|
||||
{
|
||||
int8_t RelativeX{};
|
||||
int8_t RelativeY{};
|
||||
uint16_t Tick{};
|
||||
};
|
||||
struct ChuteItem
|
||||
{
|
||||
Item Item{};
|
||||
uint16_t ChuteEntered{};
|
||||
};
|
||||
struct ChuteData
|
||||
{
|
||||
std::deque<ChuteItem> ItemsInChute{};
|
||||
};
|
||||
|
||||
public:
|
||||
Chute() = default;
|
||||
Chute(Vector2i position, const Vector<Vector2i>& chuteLinks)
|
||||
: Data{static_cast<int>(chuteLinks.size()), ChuteData{}}
|
||||
{
|
||||
for (int i{}; i < chuteLinks.size(); ++i)
|
||||
{
|
||||
Data.GetData()[i].RelativeX = position.x - chuteLinks[i].x;
|
||||
Data.GetData()[i].RelativeY = position.y - chuteLinks[i].y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
SharedBuffer<ChuteLink, ChuteData> Data{};
|
||||
};
|
||||
148
include/Components/Chute.hpp
Normal file
148
include/Components/Chute.hpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "flecs.h"
|
||||
|
||||
#include "Types/Item.hpp"
|
||||
#include "Components/Misc.hpp"
|
||||
#include "Components/Inventory.hpp"
|
||||
#include "Util/SharedBuffer.h"
|
||||
|
||||
struct ChuteConfig
|
||||
{
|
||||
float Gravity{ 1.0f };
|
||||
float MinSpeed{ 0.5f };
|
||||
};
|
||||
|
||||
struct ChuteInventoryInput : public Inventory {};
|
||||
struct ChuteInventoryOutput : public Inventory {};
|
||||
|
||||
struct Chute
|
||||
{
|
||||
struct ChuteLink
|
||||
{
|
||||
int8_t RelativeX{};
|
||||
int8_t RelativeY{};
|
||||
uint16_t Tick{};
|
||||
};
|
||||
struct ChuteItem
|
||||
{
|
||||
Item ItemInfo{};
|
||||
uint16_t ChuteEntered{}; // use TickCounter - ChuteEntered to get the time it has been in the chute
|
||||
};
|
||||
struct ChuteData
|
||||
{
|
||||
std::deque<ChuteItem> ItemsInChute{};
|
||||
uint16_t TicksToReachEnd{};
|
||||
uint16_t TickCounter{};
|
||||
};
|
||||
|
||||
public:
|
||||
Chute() = default;
|
||||
Chute(const std::vector<Vector2>& positions, const ChuteConfig& config = {})
|
||||
: Data{static_cast<int>(positions.size() - 1), ChuteData{}}
|
||||
{
|
||||
float velocity = 0.0f;
|
||||
float totalTicks = 0.0f;
|
||||
|
||||
for (size_t i = 1; i < positions.size(); ++i)
|
||||
{
|
||||
int dx = positions[i].X - positions[i - 1].X;
|
||||
int dy = positions[i].Y - positions[i - 1].Y;
|
||||
|
||||
// accumulate velocity from vertical drop (dy <= 0, so -dy >= 0)
|
||||
velocity += config.Gravity * static_cast<float>(std::abs(dy));
|
||||
velocity = std::max(velocity, config.MinSpeed);
|
||||
|
||||
float distance = std::max(1.0f, std::sqrt(static_cast<float>(dx * dx + dy * dy)));
|
||||
float linkTicks = distance / velocity;
|
||||
|
||||
Data[i - 1].RelativeX = static_cast<int8_t>(dx);
|
||||
Data[i - 1].RelativeY = static_cast<int8_t>(dy);
|
||||
Data[i - 1].Tick = static_cast<uint16_t>(std::ceil(linkTicks));
|
||||
|
||||
totalTicks += linkTicks;
|
||||
}
|
||||
|
||||
Data.GetMetaData()->TicksToReachEnd = static_cast<uint16_t>(std::ceil(totalTicks));
|
||||
}
|
||||
|
||||
void PushItem(Item item)
|
||||
{
|
||||
auto* meta = Data.GetMetaData();
|
||||
meta->ItemsInChute.push_back(ChuteItem{ .ItemInfo = item, .ChuteEntered = meta->TickCounter });
|
||||
}
|
||||
|
||||
public:
|
||||
SharedBuffer<ChuteLink, ChuteData> Data{};
|
||||
};
|
||||
|
||||
inline void Flecs_Chute(flecs::world& world)
|
||||
{
|
||||
world.component<ChuteInventoryInput>().is_a<Inventory>();
|
||||
world.component<ChuteInventoryOutput>().is_a<Inventory>();
|
||||
|
||||
world.component<ChuteConfig>()
|
||||
.member<float>("Gravity")
|
||||
.member<float>("MinSpeed");
|
||||
|
||||
world.component<Chute>();
|
||||
|
||||
// tick the chute counter
|
||||
world.system<Chute>("Chute Tick")
|
||||
.kind(flecs::PreUpdate)
|
||||
.each([](Chute& chute) {
|
||||
chute.Data.GetMetaData()->TickCounter++;
|
||||
});
|
||||
|
||||
// pull items from input inventory into chute
|
||||
world.system<Chute, ChuteInventoryInput>("Chute Input")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](Chute& chute, ChuteInventoryInput& input) {
|
||||
for (uint16_t i = 0; i < input.Slots.GetSize(); ++i)
|
||||
{
|
||||
while (input.Slots[i] > 0)
|
||||
{
|
||||
input.Slots[i]--;
|
||||
chute.PushItem(Item{ i });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// pop items that have reached the end and deposit into output inventory
|
||||
world.system<Chute, ChuteInventoryOutput, WorldInventory>("Chute Output")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](Chute& chute, ChuteInventoryOutput& output, WorldInventory& worldInv) {
|
||||
auto* meta = chute.Data.GetMetaData();
|
||||
while (!meta->ItemsInChute.empty())
|
||||
{
|
||||
auto& front = meta->ItemsInChute.front();
|
||||
uint16_t elapsed = meta->TickCounter - front.ChuteEntered;
|
||||
if (elapsed < meta->TicksToReachEnd)
|
||||
break;
|
||||
|
||||
uint16_t itemID = front.ItemInfo.ItemID;
|
||||
meta->ItemsInChute.pop_front();
|
||||
|
||||
if (!output.IsFull(itemID))
|
||||
output.AddItems(itemID, 1);
|
||||
else
|
||||
worldInv.AddItems(itemID, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inline void Chute_Helper(const flecs::entity& entity,
|
||||
const std::vector<Vector2>& positions,
|
||||
const Inventory& sourceInventory,
|
||||
const Inventory& destInventory,
|
||||
const ChuteConfig& config = {})
|
||||
{
|
||||
entity.set<Chute>(Chute{positions, config});
|
||||
entity.set<ChuteInventoryInput>(ChuteInventoryInput{sourceInventory});
|
||||
entity.set<ChuteInventoryOutput>(ChuteInventoryOutput{destInventory});
|
||||
}
|
||||
@@ -18,7 +18,8 @@ struct InventoryT
|
||||
|
||||
public:
|
||||
InventoryT() = default;
|
||||
InventoryT(size_t itemAmount) { Slots = { itemAmount, InventoryMeta{} }; }
|
||||
InventoryT(size_t itemAmount) { Slots = { static_cast<int>(itemAmount), InventoryMeta{} }; }
|
||||
InventoryT(size_t itemAmount, IntegralType maxAmount) { Slots = { static_cast<int>(itemAmount), InventoryMeta{ maxAmount } }; }
|
||||
|
||||
public:
|
||||
IntegralType GetItemsAmount(uint16_t id) const { Assert(id); return Slots[id]; }
|
||||
@@ -41,13 +42,15 @@ public:
|
||||
Slots[i] += other.Slots[i];
|
||||
}
|
||||
|
||||
bool IsFull(uint16_t id) const { Assert(id); return Slots[id] >= Slots.GetMetaData()->MaxSize; }
|
||||
|
||||
void Clear()
|
||||
{
|
||||
for (IntegralType i{}; i < Slots.GetSize(); ++i)
|
||||
Slots[i] = 0;
|
||||
}
|
||||
|
||||
void Assert(uint16_t id)
|
||||
void Assert(uint16_t id) const
|
||||
{
|
||||
DEV_ASSERT(id < Slots.GetSize() && id != Item::null);
|
||||
}
|
||||
@@ -91,7 +94,7 @@ struct FixedInventoryBase
|
||||
|
||||
tcb::span<FixedInventoryEntry> GetInventoryData()
|
||||
{
|
||||
return {&Data[0], InventorySize);
|
||||
return {&Data[0], InventorySize};
|
||||
}
|
||||
tcb::span<const FixedInventoryEntry> GetInventoryData() const
|
||||
{
|
||||
@@ -124,6 +127,9 @@ struct InventoryAreaOfEffect
|
||||
};
|
||||
static_assert(sizeof(InventoryAreaOfEffect) == 1);
|
||||
|
||||
struct InventoryOwner
|
||||
{};
|
||||
|
||||
struct ItemProcessor
|
||||
{
|
||||
ItemProcessor() = default;
|
||||
@@ -140,8 +146,9 @@ inline void Flecs_Inventory(flecs::world& world)
|
||||
.member<uint16_t>("Amount")
|
||||
.member<uint16_t>("MaxAmount");
|
||||
|
||||
world.component<FixedInventory1>()
|
||||
.add(flecs::Inheritable)
|
||||
auto fixedInv1 = world.component<FixedInventory1>();
|
||||
fixedInv1.add(flecs::Inheritable);
|
||||
fixedInv1
|
||||
.opaque(world.vector<FixedInventoryEntry>())
|
||||
.serialize([](const flecs::serializer *s, const FixedInventory1 *data) {
|
||||
for (uint8_t i = 0; i < data->InventorySize; ++i)
|
||||
@@ -151,7 +158,7 @@ inline void Flecs_Inventory(flecs::world& world)
|
||||
.count([](const FixedInventory1 *data) -> size_t {
|
||||
return data->InventorySize;
|
||||
})
|
||||
.ensure_element([](FixedInventory1 *data, size_t elem) -> FixedInventoryEntry* {
|
||||
.ensure_element([](FixedInventory1 *data, size_t elem) -> void* {
|
||||
return &data->Data[elem];
|
||||
});
|
||||
|
||||
@@ -163,7 +170,9 @@ inline void Flecs_Inventory(flecs::world& world)
|
||||
world.component<FixedInventory7>().is_a<FixedInventory1>();
|
||||
world.component<FixedInventory8>().is_a<FixedInventory1>();
|
||||
|
||||
world.component<Inventory>()
|
||||
auto inv = world.component<Inventory>();
|
||||
inv.add(flecs::Inheritable);
|
||||
inv
|
||||
.opaque(world.vector<uint32_t>())
|
||||
.serialize([](const flecs::serializer *s, const Inventory *data) {
|
||||
if (!data->Slots) return 0;
|
||||
@@ -176,16 +185,17 @@ inline void Flecs_Inventory(flecs::world& world)
|
||||
return data->Slots.GetSize();
|
||||
});
|
||||
|
||||
world.component<WorldInventory>()
|
||||
.add(flecs::Singleton)
|
||||
auto worldInv = world.component<WorldInventory>();
|
||||
worldInv.add(flecs::Singleton);
|
||||
worldInv
|
||||
.opaque(world.vector<uint64_t>())
|
||||
.serialize([](const flecs::serializer *s, const Inventory *data) {
|
||||
.serialize([](const flecs::serializer *s, const WorldInventory *data) {
|
||||
if (!data->Slots) return 0;
|
||||
for (uint64_t i = 0; i < data->Slots.GetSize(); ++i)
|
||||
s->value(data->Slots[i]);
|
||||
return 0;
|
||||
})
|
||||
.count([](const Inventory *data) -> size_t {
|
||||
.count([](const WorldInventory *data) -> size_t {
|
||||
if (!data->Slots) return 0;
|
||||
return data->Slots.GetSize();
|
||||
});
|
||||
@@ -196,7 +206,7 @@ inline void Flecs_Inventory(flecs::world& world)
|
||||
.member<uint8_t>("Size"))
|
||||
.serialize([](const flecs::serializer *s, const InventoryAreaOfEffect *data) {
|
||||
uint8_t isCircle = data->IsCircle;
|
||||
uint8_t size = data->Size;
|
||||
uint8_t size = data->ShapeSize;
|
||||
s->member("IsCircle");
|
||||
s->value(isCircle);
|
||||
s->member("Size");
|
||||
@@ -206,4 +216,13 @@ inline void Flecs_Inventory(flecs::world& world)
|
||||
|
||||
world.component<ItemProcessor>()
|
||||
.member<uint32_t>("ProcessedTicks");
|
||||
|
||||
world.component<InventoryOwner>();
|
||||
|
||||
}
|
||||
|
||||
inline void Inventory_Helper(const flecs::entity& entity, const WorldConfig& config, uint32_t maxPerSlot)
|
||||
{
|
||||
entity.set<Inventory>(Inventory{config.GetItems().size(), maxPerSlot});
|
||||
entity.add<InventoryOwner>();
|
||||
}
|
||||
@@ -20,6 +20,20 @@ struct TilePosition
|
||||
Vector2 Position;
|
||||
};
|
||||
|
||||
struct Bounds
|
||||
{
|
||||
Vector2 Min;
|
||||
Vector2 Max;
|
||||
|
||||
bool HasPoint(int x, int y) const { return x >= Min.X && x < Max.X && y >= Min.Y && y < Max.Y; }
|
||||
Bounds Grow(int32_t amount) const {
|
||||
return Bounds{
|
||||
Vector2{Min.X - amount, Min.Y - amount},
|
||||
Vector2{Max.X + amount, Max.Y + amount}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
struct Level
|
||||
{
|
||||
Level() = default;
|
||||
@@ -59,6 +73,10 @@ inline void Flecs_Misc(flecs::world& world)
|
||||
world.component<TilePosition>()
|
||||
.member<Vector2>("Position");
|
||||
|
||||
world.component<Bounds>()
|
||||
.member<Vector2>("Min")
|
||||
.member<Vector2>("Max");
|
||||
|
||||
world.component<Level>()
|
||||
.member<uint8_t>("Val");
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ struct ResourceHealth
|
||||
{
|
||||
uint16_t MaxHealth{};
|
||||
uint16_t Health{};
|
||||
uint16_t RenewalTicks{};
|
||||
uint16_t Renewal{};
|
||||
};
|
||||
|
||||
struct ResourceTick : public TickAccumulator
|
||||
@@ -26,6 +24,12 @@ struct ResourceTick : public TickAccumulator
|
||||
struct Renewing
|
||||
{};
|
||||
|
||||
struct RenewingTick : public TickAccumulator
|
||||
{};
|
||||
|
||||
struct FullyGrown
|
||||
{};
|
||||
|
||||
inline void Flecs_Resource(flecs::world& world)
|
||||
{
|
||||
world.component<ResourceInfo>()
|
||||
@@ -33,23 +37,64 @@ inline void Flecs_Resource(flecs::world& world)
|
||||
|
||||
world.component<ResourceHealth>()
|
||||
.member<uint16_t>("MaxHealth")
|
||||
.member<uint16_t>("Health")
|
||||
.member<uint16_t>("RenewalTicks")
|
||||
.member<uint16_t>("Renewal");
|
||||
.member<uint16_t>("Health");
|
||||
|
||||
world.component<ResourceTick>()
|
||||
.is_a<TickAccumulator>();
|
||||
|
||||
world.component<RenewingTick>()
|
||||
.is_a<TickAccumulator>();
|
||||
|
||||
world.component<Renewing>()
|
||||
.add<Freezes, ResourceTick>();
|
||||
|
||||
world.system<ResourceInfo, ResourceTick>()
|
||||
.without<ResourceHealth>()
|
||||
.with<WorldInventory>()
|
||||
.each([](ResourceInfo info, ResourceTick tick, WorldInventory& worldInventory) {
|
||||
if (tick.Finished()) worldInventory.AddItems(info.ResourceID, 1);
|
||||
// harvesting resource to inventory
|
||||
world.system<const ResourceInfo, const ResourceTick, WorldInventory, Inventory*>()
|
||||
.kind(flecs::OnUpdate)
|
||||
.without<Renewing>()
|
||||
.each([](ResourceInfo info, ResourceTick tick, WorldInventory& worldInventory, Inventory* optionalInventory) {
|
||||
if (tick.Finished())
|
||||
{
|
||||
bool pushToLocalInventory = optionalInventory && !optionalInventory->IsFull(info.ResourceID);
|
||||
|
||||
if (pushToLocalInventory) optionalInventory->AddItems(info.ResourceID, 1);
|
||||
else worldInventory.AddItems(info.ResourceID, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// decrease health if ResourceHealth component
|
||||
world.system<ResourceHealth, const ResourceTick>()
|
||||
.kind(flecs::OnUpdate)
|
||||
.without<Renewing>()
|
||||
.each([](ResourceHealth& health, ResourceTick tick){
|
||||
health.Health -= tick.Finished();
|
||||
});
|
||||
|
||||
// checking if we have to renew the resource
|
||||
world.system<const ResourceHealth>()
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<RenewingTick>()
|
||||
.without<Renewing>()
|
||||
.each([](flecs::entity entity, ResourceHealth health) {
|
||||
if (health.Health == 0) {
|
||||
entity.remove<FullyGrown>();
|
||||
entity.add<Renewing>();
|
||||
entity.ensure<RenewingTick>().AccumulatedTick = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// finish renewing
|
||||
world.system<ResourceHealth, RenewingTick>("Finish Renewing")
|
||||
.kind(flecs::OnUpdate)
|
||||
.with<Renewing>()
|
||||
.each([](flecs::entity entity, ResourceHealth& health, RenewingTick& tick) {
|
||||
if (tick.Finished()) {
|
||||
health.Health = health.MaxHealth;
|
||||
entity.remove<Renewing>();
|
||||
entity.add<FullyGrown>();
|
||||
entity.ensure<ResourceTick>().AccumulatedTick = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inline void Resource_Ore_Helper(const flecs::entity& entity, uint16_t resourceID, uint16_t gatherTicks)
|
||||
@@ -60,6 +105,23 @@ inline void Resource_Ore_Helper(const flecs::entity& entity, uint16_t resourceID
|
||||
ResourceTick tick{};
|
||||
tick.MaxTick = gatherTicks;
|
||||
|
||||
entity.add<ResourceInfo>(info);
|
||||
entity.add<ResourceTick>(tick);
|
||||
entity.set<ResourceInfo>(info);
|
||||
entity.set<ResourceTick>(tick);
|
||||
}
|
||||
|
||||
inline void Resource_Tree_Helper(const flecs::entity& entity, uint16_t resourceID,
|
||||
uint16_t gatherTicks, uint16_t maxHealth, uint16_t renewalTicks)
|
||||
{
|
||||
Resource_Ore_Helper(entity, resourceID, gatherTicks);
|
||||
|
||||
ResourceHealth health{};
|
||||
health.MaxHealth = maxHealth;
|
||||
health.Health = maxHealth;
|
||||
|
||||
RenewingTick tick{};
|
||||
tick.MaxTick = renewalTicks;
|
||||
|
||||
entity.set<ResourceHealth>(health);
|
||||
entity.set<RenewingTick>(tick);
|
||||
entity.add<FullyGrown>();
|
||||
}
|
||||
@@ -2,18 +2,33 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "flecs.h"
|
||||
|
||||
#include "Components/Misc.hpp"
|
||||
#include "Util/Span.h"
|
||||
|
||||
struct Support
|
||||
{
|
||||
uint8_t MaxSupport : 5;
|
||||
bool SupportsUp : 1;
|
||||
bool SupportsRight : 1;
|
||||
bool SupportsLeft : 1;
|
||||
uint8_t SupportsAvailable : 5;
|
||||
bool SupportedByBottom : 1;
|
||||
bool SupportedByRight : 1;
|
||||
bool SupportedByLeft : 1;
|
||||
uint8_t MaxSupport{};
|
||||
uint8_t SupportsAvailable{};
|
||||
};
|
||||
|
||||
struct RequiresSupport
|
||||
struct GroundedSupport {};
|
||||
struct RequiresSupport {};
|
||||
|
||||
flecs::entity GetSupport(flecs::world& world, Vector2 pos);
|
||||
|
||||
void RecalculateSupport(flecs::world& world,
|
||||
tcb::span<const Vector2> skip = {},
|
||||
tcb::span<const Vector2> unground = {});
|
||||
|
||||
bool CanRemove(flecs::world& world, tcb::span<const Vector2> positions);
|
||||
|
||||
void Flecs_Support(flecs::world& world);
|
||||
|
||||
inline void Support_Helper(const flecs::entity& entity, Vector2 pos, uint8_t maxSupport, bool grounded = false)
|
||||
{
|
||||
};
|
||||
entity.set<TilePosition>({ pos });
|
||||
if (grounded) entity.add<GroundedSupport>(); // before set<Support> so OnAdd fires with grounded state visible
|
||||
entity.set<Support>({ maxSupport });
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// #pragma once
|
||||
// #include "core/object/ref_counted.h"
|
||||
|
||||
// struct Sync
|
||||
// { };
|
||||
|
||||
// class FactoryEntity;
|
||||
|
||||
// struct NodePtr
|
||||
// {
|
||||
// NodePtr() = default;
|
||||
// NodePtr(FactoryEntity* node) : Node{ node } {}
|
||||
|
||||
// template <typename T>
|
||||
// T* AsNode() const
|
||||
// {
|
||||
// static_assert(std::is_base_of_v<FactoryEntity, T>);
|
||||
// DEV_ASSERT(Object::cast_to<T>(Node));
|
||||
// return static_cast<T*>(Node);
|
||||
// }
|
||||
|
||||
// FactoryEntity* Node{};
|
||||
// };
|
||||
|
||||
// class Archetype;
|
||||
|
||||
// struct ArchetypePtr
|
||||
// {
|
||||
// ArchetypePtr() = default;
|
||||
// ArchetypePtr(Ref<Archetype>& archetpye) : Archetype{ archetpye } {}
|
||||
|
||||
// Ref<Archetype> Archetype;
|
||||
// };
|
||||
91
include/Components/WorldGraph.hpp
Normal file
91
include/Components/WorldGraph.hpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include "flecs.h"
|
||||
#include "Types/WorldGraph/WorldGraphVisualNode.h"
|
||||
|
||||
// Register all WorldGraph components with Flecs reflection so they can be
|
||||
// serialized to/from JSON via world.to_json() / world.from_json().
|
||||
|
||||
inline void Flecs_WorldGraph(flecs::world& world)
|
||||
{
|
||||
// ── Node kind enum ──────────────────────────────────────────────────────
|
||||
world.component<VisualNodeKind>()
|
||||
.constant("Add", VisualNodeKind::Add)
|
||||
.constant("Subtract", VisualNodeKind::Subtract)
|
||||
.constant("Multiply", VisualNodeKind::Multiply)
|
||||
.constant("Divide", VisualNodeKind::Divide)
|
||||
.constant("Modulo", VisualNodeKind::Modulo)
|
||||
.constant("Pow", VisualNodeKind::Pow)
|
||||
.constant("Max", VisualNodeKind::Max)
|
||||
.constant("Min", VisualNodeKind::Min)
|
||||
.constant("Negate", VisualNodeKind::Negate)
|
||||
.constant("Abs", VisualNodeKind::Abs)
|
||||
.constant("Ceil", VisualNodeKind::Ceil)
|
||||
.constant("Floor", VisualNodeKind::Floor)
|
||||
.constant("Sin", VisualNodeKind::Sin)
|
||||
.constant("Cos", VisualNodeKind::Cos)
|
||||
.constant("Tan", VisualNodeKind::Tan)
|
||||
.constant("Exp", VisualNodeKind::Exp)
|
||||
.constant("Log", VisualNodeKind::Log)
|
||||
.constant("Square", VisualNodeKind::Square)
|
||||
.constant("Round", VisualNodeKind::Round)
|
||||
.constant("OneMinus", VisualNodeKind::OneMinus)
|
||||
.constant("Lerp", VisualNodeKind::Lerp)
|
||||
.constant("Clamp", VisualNodeKind::Clamp)
|
||||
.constant("Equal", VisualNodeKind::Equal)
|
||||
.constant("Smaller", VisualNodeKind::Smaller)
|
||||
.constant("Greater", VisualNodeKind::Greater)
|
||||
.constant("SmallerEqual", VisualNodeKind::SmallerEqual)
|
||||
.constant("GreaterEqual", VisualNodeKind::GreaterEqual)
|
||||
.constant("And", VisualNodeKind::And)
|
||||
.constant("Or", VisualNodeKind::Or)
|
||||
.constant("Branch", VisualNodeKind::Branch)
|
||||
.constant("Simplex", VisualNodeKind::Simplex)
|
||||
.constant("OpenSimplex", VisualNodeKind::OpenSimplex)
|
||||
.constant("Perlin", VisualNodeKind::Perlin)
|
||||
.constant("Value", VisualNodeKind::Value)
|
||||
.constant("ValueCubic", VisualNodeKind::ValueCubic)
|
||||
.constant("Constant", VisualNodeKind::Constant)
|
||||
.constant("IsTile", VisualNodeKind::IsTile)
|
||||
.constant("TileDistance", VisualNodeKind::TileDistance);
|
||||
|
||||
// ── TileType enum (needed by IsTile / TileDistance params) ─────────────
|
||||
world.component<TileType>()
|
||||
.constant("Air", TileType::Air)
|
||||
.constant("Filler", TileType::Filler)
|
||||
.constant("Liquid", TileType::Liquid)
|
||||
.constant("Ore", TileType::Ore)
|
||||
.constant("NPC", TileType::NPC)
|
||||
.constant("Plant", TileType::Plant);
|
||||
|
||||
// ── Core node components ────────────────────────────────────────────────
|
||||
world.component<VisualNodeType>()
|
||||
.member<VisualNodeKind>("kind");
|
||||
|
||||
world.component<VisualNodePos>()
|
||||
.member<float>("x")
|
||||
.member<float>("y");
|
||||
|
||||
world.component<VisualNodeOutput>();
|
||||
|
||||
// ── Connection relationship tags ────────────────────────────────────────
|
||||
world.component<InputPin0>();
|
||||
world.component<InputPin1>();
|
||||
world.component<InputPin2>();
|
||||
|
||||
// ── Leaf parameter components ───────────────────────────────────────────
|
||||
world.component<NodeParam_Constant>()
|
||||
.member<float>("value");
|
||||
|
||||
world.component<NodeParam_Noise>()
|
||||
.member<float>("frequency");
|
||||
|
||||
world.component<NodeParam_IsTile>()
|
||||
.member<int8_t>("relativeX")
|
||||
.member<int8_t>("relativeY")
|
||||
.member<TileType>("tileType");
|
||||
|
||||
world.component<NodeParam_TileDistance>()
|
||||
.member<int8_t>("range")
|
||||
.member<TileType>("tileType");
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "modules/factory/include/Data/Tile.h"
|
||||
#include "core/math/vector2i.h"
|
||||
#include <stdint.h>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Util/Span.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "Types/Tile.h"
|
||||
#include "Components/Misc.hpp"
|
||||
|
||||
struct Chunk
|
||||
{
|
||||
@@ -23,22 +30,22 @@ public:
|
||||
static void Assert(int x, int y) { DEV_ASSERT(x < ChunkSize && x >= 0 && y < ChunkSize && y >= 0); }
|
||||
public:
|
||||
Tile GetTile(int x, int y) const { Assert(x, y); return Tiles[y * ChunkSize + x]; }
|
||||
Tile GetTile(Vector2i pos) const { return GetTile(pos.x, pos.y); }
|
||||
Tile GetTile(Vector2 pos) const { return GetTile(pos.X, pos.Y); }
|
||||
const Tile& GetTileRef(int x, int y) const { Assert(x, y); return Tiles[y * ChunkSize + x]; }
|
||||
Tile& GetTile(int x, int y) { Assert(x, y); return Tiles[y * ChunkSize + x]; }
|
||||
Tile& GetTile(Vector2i pos) { return GetTile(pos.x, pos.y); }
|
||||
Tile& GetTile(Vector2 pos) { return GetTile(pos.X, pos.Y); }
|
||||
};
|
||||
|
||||
struct ChunkCoordinate
|
||||
{
|
||||
ChunkCoordinate() = default;
|
||||
ChunkCoordinate(int x, int y) : X{ static_cast<uint8_t>(x) }, Y{ static_cast<uint8_t>(y) } { DEV_ASSERT(x >= 0 && x < Chunk::ChunkSize && y >= 0 && y < Chunk::ChunkSize); }
|
||||
ChunkCoordinate(Vector2i pos) : ChunkCoordinate{pos.x, pos.y} {}
|
||||
ChunkCoordinate(Vector2 pos) : ChunkCoordinate{pos.X, pos.Y} {}
|
||||
|
||||
uint8_t X{};
|
||||
uint8_t Y{};
|
||||
|
||||
operator Vector2i() const { return Vector2i(X, Y); }
|
||||
operator Vector2() const { return Vector2(X, Y); }
|
||||
};
|
||||
|
||||
static_assert(sizeof(ChunkCoordinate) / 2 >= Chunk::ChunkSizePowerOfTwo / 8);
|
||||
@@ -47,14 +54,14 @@ struct EntityTile final
|
||||
{
|
||||
public:
|
||||
EntityTile() = default;
|
||||
EntityTile(entt::entity entity, int worldX, int worldY)
|
||||
EntityTile(flecs::entity entity, int worldX, int worldY)
|
||||
: Entity{ entity }
|
||||
, ChunkX{ Chunk::WorldToLocal(worldX) }
|
||||
, ChunkY{ Chunk::WorldToLocal(worldY) }
|
||||
{}
|
||||
|
||||
public:
|
||||
entt::entity Entity{};
|
||||
flecs::entity Entity{};
|
||||
Chunk::CoordinateType ChunkX{};
|
||||
Chunk::CoordinateType ChunkY{};
|
||||
};
|
||||
@@ -77,7 +84,7 @@ public:
|
||||
uint32_t hash() const { return static_cast<uint32_t>(hash64()); }
|
||||
static uint32_t hash(ChunkKey key) { return key.hash(); }
|
||||
|
||||
Rect2i GetBounds() const { return Rect2i{ChunkToWorld(X), ChunkToWorld(Y), 1 << Chunk::ChunkSizePowerOfTwo, 1 << Chunk::ChunkSizePowerOfTwo}; }
|
||||
Bounds GetBounds() const { return Bounds{Vector2{ChunkToWorld(X), ChunkToWorld(Y)}, Vector2{ChunkToWorld(X + 1), ChunkToWorld(Y + 1)}}; }
|
||||
|
||||
public:
|
||||
bool operator==(const ChunkKey& rhs) const { return hash() == rhs.hash(); }
|
||||
@@ -88,16 +95,16 @@ static_assert(sizeof(ChunkKey) == 4);
|
||||
struct ChunkData
|
||||
{
|
||||
public:
|
||||
void MarkAsPersistant(entt::entity entity);
|
||||
void RemovePersistance(entt::entity entity);
|
||||
void MarkAsPersistant(flecs::entity entity);
|
||||
void RemovePersistance(flecs::entity entity);
|
||||
void Clear()
|
||||
{
|
||||
Chunk = {};
|
||||
TileData = {};
|
||||
Entities.resize(0);
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<Chunk> Chunk{};
|
||||
std::unique_ptr<Chunk> TileData{};
|
||||
std::vector<EntityTile> Entities{};
|
||||
std::vector<EntityTile> PersistantEntities{};
|
||||
};
|
||||
@@ -114,17 +121,17 @@ public:
|
||||
|
||||
Tile GetTile(int x, int y);
|
||||
Tile const* TryGetTile(int x, int y) const;
|
||||
entt::entity GetEntity(int x, int y) const;
|
||||
flecs::entity GetEntity(int x, int y) const;
|
||||
|
||||
void SetChunkTiles(int x, int y, std::unique_ptr<Chunk>&& chunk);
|
||||
void SetChunkTiles(ChunkKey key, std::unique_ptr<Chunk>&& chunk);
|
||||
void AddEntity(entt::entity entity, const Vector<Vector2i>& claimedPositions);
|
||||
void AddPersistantEntity(entt::entity entity, const Vector<Vector2i>& claimedPositions);
|
||||
void AddEntity(flecs::entity entity, tcb::span<Vector2> claimedPositions);
|
||||
void AddPersistantEntity(flecs::entity entity, tcb::span<Vector2> claimedPositions);
|
||||
|
||||
void MarkAsPersistant(entt::entity entity);
|
||||
void RemovePersistance(entt::entity entity);
|
||||
void MarkAsPersistant(flecs::entity entity);
|
||||
void RemovePersistance(flecs::entity entity);
|
||||
|
||||
void RemoveEntity(entt::entity entity);
|
||||
void RemoveEntity(flecs::entity entity);
|
||||
void RemoveChunk(int x, int y);
|
||||
void RemoveChunk(ChunkKey key);
|
||||
|
||||
@@ -143,7 +150,7 @@ private:
|
||||
|
||||
private:
|
||||
std::vector<ChunkData> ChunkDatas;
|
||||
HashMap<ChunkKey, int, ChunkKey> ChunkMap{};
|
||||
std::unordered_map<ChunkKey, int, ChunkKey> ChunkMap{};
|
||||
ChunkKey CachedChunkKey{};
|
||||
int CachedChunk{-1};
|
||||
};
|
||||
|
||||
@@ -1,197 +1,197 @@
|
||||
#pragma once
|
||||
#include "Util/StackAllocator.h"
|
||||
#include "Util/Span.h"
|
||||
#include "EnTT/entity/registry.hpp"
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include "Components/Sync.h"
|
||||
#include "Core/FactoryWorld.h"
|
||||
// #pragma once
|
||||
// #include "Util/StackAllocator.h"
|
||||
// #include "Util/Span.h"
|
||||
// #include "EnTT/entity/registry.hpp"
|
||||
// #include <functional>
|
||||
// #include <mutex>
|
||||
// #include "Components/Sync.h"
|
||||
// #include "Core/FactoryWorld.h"
|
||||
|
||||
struct FactoryCommand
|
||||
{
|
||||
void* Data;
|
||||
entt::entity Entity;
|
||||
void(*Command)(FactoryWorld& world, entt::entity entity, void* data);
|
||||
};
|
||||
// struct FactoryCommand
|
||||
// {
|
||||
// void* Data;
|
||||
// entt::entity Entity;
|
||||
// void(*Command)(FactoryWorld& world, entt::entity entity, void* data);
|
||||
// };
|
||||
|
||||
class FactoryCommandQueue final
|
||||
{
|
||||
static constexpr int DefaultAllocatorSize = 1024 * 1024;
|
||||
// class FactoryCommandQueue final
|
||||
// {
|
||||
// static constexpr int DefaultAllocatorSize = 1024 * 1024;
|
||||
|
||||
public:
|
||||
FactoryCommandQueue(size_t memorySize)
|
||||
: Allocator{ memorySize }
|
||||
{}
|
||||
FactoryCommandQueue()
|
||||
: Allocator{ DefaultAllocatorSize }
|
||||
{}
|
||||
// public:
|
||||
// FactoryCommandQueue(size_t memorySize)
|
||||
// : Allocator{ memorySize }
|
||||
// {}
|
||||
// FactoryCommandQueue()
|
||||
// : Allocator{ DefaultAllocatorSize }
|
||||
// {}
|
||||
|
||||
public:
|
||||
// public:
|
||||
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
void SetComponentData(entt::entity entity, const T& component);
|
||||
// public:
|
||||
// template <typename T>
|
||||
// void SetComponentData(entt::entity entity, const T& component);
|
||||
|
||||
template <typename T>
|
||||
void SetOrAddComponentData(entt::entity entity, const T& component);
|
||||
// template <typename T>
|
||||
// void SetOrAddComponentData(entt::entity entity, const T& component);
|
||||
|
||||
template <typename T>
|
||||
void AddIfNone(entt::entity entity, const T& component);
|
||||
// template <typename T>
|
||||
// void AddIfNone(entt::entity entity, const T& component);
|
||||
|
||||
template <typename T>
|
||||
void AddComponent(entt::entity entity, const T& component);
|
||||
// template <typename T>
|
||||
// void AddComponent(entt::entity entity, const T& component);
|
||||
|
||||
template <typename T>
|
||||
void AddComponent(entt::entity entity);
|
||||
// template <typename T>
|
||||
// void AddComponent(entt::entity entity);
|
||||
|
||||
template <typename T>
|
||||
void RemoveComponent(entt::entity entity);
|
||||
// template <typename T>
|
||||
// void RemoveComponent(entt::entity entity);
|
||||
|
||||
//void RemoveEntity(entt::entity entity);
|
||||
// //void RemoveEntity(entt::entity entity);
|
||||
|
||||
void SyncEntity(entt::entity entity) { AddComponent<Sync>(entity); }
|
||||
void StopSyncingEntity(entt::entity entity) { RemoveComponent<Sync>(entity); }
|
||||
// void SyncEntity(entt::entity entity) { AddComponent<Sync>(entity); }
|
||||
// void StopSyncingEntity(entt::entity entity) { RemoveComponent<Sync>(entity); }
|
||||
|
||||
void ExecuteAll(FactoryWorld& world);
|
||||
// void ExecuteAll(FactoryWorld& world);
|
||||
|
||||
void Clear();
|
||||
// void Clear();
|
||||
|
||||
template <typename T>
|
||||
T* AllocateData()
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
return Allocator.allocate<T>(1);
|
||||
}
|
||||
// template <typename T>
|
||||
// T* AllocateData()
|
||||
// {
|
||||
// static_assert(std::is_trivially_copyable_v<T>);
|
||||
// return Allocator.allocate<T>(1);
|
||||
// }
|
||||
|
||||
template <typename T, typename ... Args>
|
||||
T* AllocateData(Args... arguments)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
T* data = AllocateData<T>();
|
||||
new (data) T(arguments...);
|
||||
return data;
|
||||
}
|
||||
// template <typename T, typename ... Args>
|
||||
// T* AllocateData(Args... arguments)
|
||||
// {
|
||||
// static_assert(std::is_trivially_copyable_v<T>);
|
||||
// T* data = AllocateData<T>();
|
||||
// new (data) T(arguments...);
|
||||
// return data;
|
||||
// }
|
||||
|
||||
template <typename T>
|
||||
tcb::span<T>* AllocateBuffer(uint32_t size)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
auto spanData = Allocator.allocate<tcb::span<T>>();
|
||||
auto data = Allocator.allocate<T>(size);
|
||||
for (uint32_t i{}; i < size; ++i)
|
||||
data[i] = {};
|
||||
*spanData = tcb::span<T>(data, size);
|
||||
return spanData;
|
||||
}
|
||||
// template <typename T>
|
||||
// tcb::span<T>* AllocateBuffer(uint32_t size)
|
||||
// {
|
||||
// static_assert(std::is_trivially_copyable_v<T>);
|
||||
// auto spanData = Allocator.allocate<tcb::span<T>>();
|
||||
// auto data = Allocator.allocate<T>(size);
|
||||
// for (uint32_t i{}; i < size; ++i)
|
||||
// data[i] = {};
|
||||
// *spanData = tcb::span<T>(data, size);
|
||||
// return spanData;
|
||||
// }
|
||||
|
||||
private:
|
||||
void ClearUnsafe()
|
||||
{
|
||||
Commands.clear();
|
||||
Allocator.reset();
|
||||
}
|
||||
// private:
|
||||
// void ClearUnsafe()
|
||||
// {
|
||||
// Commands.clear();
|
||||
// Allocator.reset();
|
||||
// }
|
||||
|
||||
private:
|
||||
StackAllocator Allocator;
|
||||
std::mutex Mutex;
|
||||
std::vector<FactoryCommand> Commands;
|
||||
};
|
||||
// private:
|
||||
// StackAllocator Allocator;
|
||||
// std::mutex Mutex;
|
||||
// std::vector<FactoryCommand> Commands;
|
||||
// };
|
||||
|
||||
template<typename T>
|
||||
inline void FactoryCommandQueue::SetComponentData(entt::entity entity, const T& component)
|
||||
{
|
||||
FactoryCommand command;
|
||||
command.Entity = entity;
|
||||
command.Data = AllocateData<T>(component);
|
||||
command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
{
|
||||
registry.get<T>(entity) = *static_cast<T*>(data);
|
||||
};
|
||||
std::scoped_lock lock(Mutex);
|
||||
Commands.push_back(command);
|
||||
}
|
||||
// template<typename T>
|
||||
// inline void FactoryCommandQueue::SetComponentData(entt::entity entity, const T& component)
|
||||
// {
|
||||
// FactoryCommand command;
|
||||
// command.Entity = entity;
|
||||
// command.Data = AllocateData<T>(component);
|
||||
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
// {
|
||||
// registry.get<T>(entity) = *static_cast<T*>(data);
|
||||
// };
|
||||
// std::scoped_lock lock(Mutex);
|
||||
// Commands.push_back(command);
|
||||
// }
|
||||
|
||||
template<typename T>
|
||||
inline void FactoryCommandQueue::SetOrAddComponentData(entt::entity entity, const T& component)
|
||||
{
|
||||
FactoryCommand command;
|
||||
command.Entity = entity;
|
||||
command.Data = AllocateData<T>(component);
|
||||
command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
{
|
||||
registry.emplace_or_replace<T>(entity, *static_cast<T*>(data));
|
||||
};
|
||||
std::scoped_lock lock(Mutex);
|
||||
Commands.push_back(command);
|
||||
}
|
||||
// template<typename T>
|
||||
// inline void FactoryCommandQueue::SetOrAddComponentData(entt::entity entity, const T& component)
|
||||
// {
|
||||
// FactoryCommand command;
|
||||
// command.Entity = entity;
|
||||
// command.Data = AllocateData<T>(component);
|
||||
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
// {
|
||||
// registry.emplace_or_replace<T>(entity, *static_cast<T*>(data));
|
||||
// };
|
||||
// std::scoped_lock lock(Mutex);
|
||||
// Commands.push_back(command);
|
||||
// }
|
||||
|
||||
template<typename T>
|
||||
inline void FactoryCommandQueue::AddIfNone(entt::entity entity, const T& component)
|
||||
{
|
||||
FactoryCommand command;
|
||||
command.Entity = entity;
|
||||
command.Data = AllocateData<T>(component);
|
||||
command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
{
|
||||
if (!registry.all_of<T>(entity))
|
||||
registry.emplace<T>(entity, *static_cast<T*>(data));
|
||||
};
|
||||
std::scoped_lock lock(Mutex);
|
||||
Commands.push_back(command);
|
||||
}
|
||||
// template<typename T>
|
||||
// inline void FactoryCommandQueue::AddIfNone(entt::entity entity, const T& component)
|
||||
// {
|
||||
// FactoryCommand command;
|
||||
// command.Entity = entity;
|
||||
// command.Data = AllocateData<T>(component);
|
||||
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
// {
|
||||
// if (!registry.all_of<T>(entity))
|
||||
// registry.emplace<T>(entity, *static_cast<T*>(data));
|
||||
// };
|
||||
// std::scoped_lock lock(Mutex);
|
||||
// Commands.push_back(command);
|
||||
// }
|
||||
|
||||
template<typename T>
|
||||
inline void FactoryCommandQueue::AddComponent(entt::entity entity, const T& component)
|
||||
{
|
||||
FactoryCommand command;
|
||||
command.Entity = entity;
|
||||
command.Data = AllocateData<T>(component);
|
||||
command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
{
|
||||
registry.emplace<T>(entity, *static_cast<T*>(data));
|
||||
};
|
||||
std::scoped_lock lock(Mutex);
|
||||
Commands.push_back(command);
|
||||
}
|
||||
// template<typename T>
|
||||
// inline void FactoryCommandQueue::AddComponent(entt::entity entity, const T& component)
|
||||
// {
|
||||
// FactoryCommand command;
|
||||
// command.Entity = entity;
|
||||
// command.Data = AllocateData<T>(component);
|
||||
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
// {
|
||||
// registry.emplace<T>(entity, *static_cast<T*>(data));
|
||||
// };
|
||||
// std::scoped_lock lock(Mutex);
|
||||
// Commands.push_back(command);
|
||||
// }
|
||||
|
||||
template<typename T>
|
||||
inline void FactoryCommandQueue::AddComponent(entt::entity entity)
|
||||
{
|
||||
FactoryCommand command;
|
||||
command.Entity = entity;
|
||||
command.Data = nullptr;
|
||||
command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
{
|
||||
registry.emplace<T>(entity);
|
||||
};
|
||||
std::scoped_lock lock(Mutex);
|
||||
Commands.push_back(command);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void FactoryCommandQueue::RemoveComponent(entt::entity entity)
|
||||
{
|
||||
FactoryCommand command;
|
||||
command.Entity = entity;
|
||||
command.Data = nullptr;
|
||||
command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
{
|
||||
registry.remove<T>(entity);
|
||||
};
|
||||
std::scoped_lock lock(Mutex);
|
||||
Commands.push_back(command);
|
||||
}
|
||||
|
||||
// inline void FactoryCommandQueue::RemoveEntity(entt::entity entity)
|
||||
// template<typename T>
|
||||
// inline void FactoryCommandQueue::AddComponent(entt::entity entity)
|
||||
// {
|
||||
// FactoryCommand command;
|
||||
// command.Entity = entity;
|
||||
// command.Data = nullptr;
|
||||
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
// {
|
||||
// registry.destroy(entity);
|
||||
// registry.emplace<T>(entity);
|
||||
// };
|
||||
// std::scoped_lock lock(Mutex);
|
||||
// Commands.push_back(command);
|
||||
// }
|
||||
|
||||
// template<typename T>
|
||||
// inline void FactoryCommandQueue::RemoveComponent(entt::entity entity)
|
||||
// {
|
||||
// FactoryCommand command;
|
||||
// command.Entity = entity;
|
||||
// command.Data = nullptr;
|
||||
// command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
// {
|
||||
// registry.remove<T>(entity);
|
||||
// };
|
||||
// std::scoped_lock lock(Mutex);
|
||||
// Commands.push_back(command);
|
||||
// }
|
||||
|
||||
// // inline void FactoryCommandQueue::RemoveEntity(entt::entity entity)
|
||||
// // {
|
||||
// // FactoryCommand command;
|
||||
// // command.Entity = entity;
|
||||
// // command.Data = nullptr;
|
||||
// // command.Command = [](entt::registry& registry, entt::entity entity, void* data)
|
||||
// // {
|
||||
// // registry.destroy(entity);
|
||||
// // };
|
||||
// // std::scoped_lock lock(Mutex);
|
||||
// // Commands.push_back(command);
|
||||
// // }
|
||||
@@ -1,159 +1,159 @@
|
||||
#pragma once
|
||||
// #pragma once
|
||||
|
||||
#include <array>
|
||||
#include "core/templates/vector.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
#include "modules/noise/fastnoise_lite.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "scene/2d/tile_map_layer.h"
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/math/vector2i.h"
|
||||
// #include <array>
|
||||
// #include "core/templates/vector.h"
|
||||
// #include "core/templates/hash_map.h"
|
||||
// #include "core/object/ref_counted.h"
|
||||
// #include "modules/noise/fastnoise_lite.h"
|
||||
// #include "core/io/resource.h"
|
||||
// #include "scene/2d/tile_map_layer.h"
|
||||
// #include "core/os/mutex.h"
|
||||
// #include "core/templates/hash_set.h"
|
||||
// #include "core/math/vector2i.h"
|
||||
|
||||
#include "Components/Position.h"
|
||||
#include "Components/Inventory.h"
|
||||
#include "Components/Support.h"
|
||||
// #include "Components/Position.h"
|
||||
// #include "Components/Inventory.h"
|
||||
// #include "Components/Support.h"
|
||||
|
||||
#include "Data/Tile.h"
|
||||
#include "Util/ResourceAccess.h"
|
||||
#include "Chunk.h"
|
||||
#include "SystemBase.h"
|
||||
#include "Data/WorldSettings.h"
|
||||
#include "FactoryCommandQueue.h"
|
||||
#include "WorldThreadData.h"
|
||||
// #include "Data/Tile.h"
|
||||
// #include "Util/ResourceAccess.h"
|
||||
// #include "Chunk.h"
|
||||
// #include "SystemBase.h"
|
||||
// #include "Data/WorldSettings.h"
|
||||
// #include "FactoryCommandQueue.h"
|
||||
// #include "WorldThreadData.h"
|
||||
|
||||
class FactoryWorld;
|
||||
class FactoryWorldInterface;
|
||||
// class FactoryWorld;
|
||||
// class FactoryWorldInterface;
|
||||
|
||||
// typedef ResourceAccess<FactoryWorld, Mutex> FactoryWorldAccess;
|
||||
// // typedef ResourceAccess<FactoryWorld, Mutex> FactoryWorldAccess;
|
||||
|
||||
enum FactoryError
|
||||
{
|
||||
FACTORY_ERROR_NONE = 0,
|
||||
FACTORY_ERROR_NOT_ENTITY,
|
||||
FACTORY_ERROR_CANT_UPGRADE,
|
||||
FACTORY_ERROR_FULLY_UPGRADED,
|
||||
FACTORY_ERROR_NOT_ENOUGH_ITEMS,
|
||||
FACTORY_ERROR_NO_SPACE,
|
||||
FACTORY_ERROR_REQUIRES_SUPPORT,
|
||||
FACTORY_ERROR_PATH_TOO_LONG,
|
||||
FACTORY_ERROR_INVALID_PATH,
|
||||
FACTORY_ERROR_INVALID_POS,
|
||||
FACTORY_ERROR_MISC_ERROR,
|
||||
};
|
||||
// enum FactoryError
|
||||
// {
|
||||
// FACTORY_ERROR_NONE = 0,
|
||||
// FACTORY_ERROR_NOT_ENTITY,
|
||||
// FACTORY_ERROR_CANT_UPGRADE,
|
||||
// FACTORY_ERROR_FULLY_UPGRADED,
|
||||
// FACTORY_ERROR_NOT_ENOUGH_ITEMS,
|
||||
// FACTORY_ERROR_NO_SPACE,
|
||||
// FACTORY_ERROR_REQUIRES_SUPPORT,
|
||||
// FACTORY_ERROR_PATH_TOO_LONG,
|
||||
// FACTORY_ERROR_INVALID_PATH,
|
||||
// FACTORY_ERROR_INVALID_POS,
|
||||
// FACTORY_ERROR_MISC_ERROR,
|
||||
// };
|
||||
|
||||
struct ChunkUnlock final
|
||||
{
|
||||
ChunkKey ChunkID{};
|
||||
Vector<ItemAmount64> Items{};
|
||||
};
|
||||
// struct ChunkUnlock final
|
||||
// {
|
||||
// ChunkKey ChunkID{};
|
||||
// Vector<ItemAmount64> Items{};
|
||||
// };
|
||||
|
||||
class FactoryWorld final
|
||||
{
|
||||
friend class WorldSerializer;
|
||||
friend class WorldLoader;
|
||||
public:
|
||||
FactoryWorld() = default;
|
||||
~FactoryWorld() = default;
|
||||
// class FactoryWorld final
|
||||
// {
|
||||
// friend class WorldSerializer;
|
||||
// friend class WorldLoader;
|
||||
// public:
|
||||
// FactoryWorld() = default;
|
||||
// ~FactoryWorld() = default;
|
||||
|
||||
public:
|
||||
void Tick(int amount = 1);
|
||||
void Initialize(FactoryWorldInterface* worldInterface);
|
||||
void Save();
|
||||
// public:
|
||||
// void Tick(int amount = 1);
|
||||
// void Initialize(FactoryWorldInterface* worldInterface);
|
||||
// void Save();
|
||||
|
||||
public:
|
||||
int32_t GetSeed() const { return Seed; }
|
||||
// public:
|
||||
// int32_t GetSeed() const { return Seed; }
|
||||
|
||||
FactoryError CanPlaceEntity(int x, int y, Ref<Archetype> archetype);
|
||||
FactoryError AddEntity(int x, int y, Ref<Archetype> archetype);
|
||||
void RemoveEntity(FactoryEntity* node);
|
||||
void RemoveEntity(int x, int y);
|
||||
void RemoveEntity(entt::entity entity);
|
||||
// FactoryError CanPlaceEntity(int x, int y, Ref<Archetype> archetype);
|
||||
// FactoryError AddEntity(int x, int y, Ref<Archetype> archetype);
|
||||
// void RemoveEntity(FactoryEntity* node);
|
||||
// void RemoveEntity(int x, int y);
|
||||
// void RemoveEntity(entt::entity entity);
|
||||
|
||||
// FactoryWorldAccess GetAccess() { return FactoryWorldAccess{ *this, WorldAccessMutex }; }
|
||||
// // FactoryWorldAccess GetAccess() { return FactoryWorldAccess{ *this, WorldAccessMutex }; }
|
||||
|
||||
entt::registry& GetRegistry() { return Registry; };
|
||||
const entt::registry& GetRegistry() const { return Registry; };
|
||||
auto& GetChunks() { return Chunks; }
|
||||
const auto& GetChunks() const { return Chunks; }
|
||||
auto& GetSettings() const { return WorldSettings; }
|
||||
// entt::registry& GetRegistry() { return Registry; };
|
||||
// const entt::registry& GetRegistry() const { return Registry; };
|
||||
// auto& GetChunks() { return Chunks; }
|
||||
// const auto& GetChunks() const { return Chunks; }
|
||||
// auto& GetSettings() const { return WorldSettings; }
|
||||
|
||||
bool IsValidCameraPos(Rect2i viewport) const;
|
||||
// bool IsValidCameraPos(Rect2i viewport) const;
|
||||
|
||||
public: // Chunks
|
||||
FactoryError TryUnlockChunk(ChunkKey chunk);
|
||||
// public: // Chunks
|
||||
// FactoryError TryUnlockChunk(ChunkKey chunk);
|
||||
|
||||
private:
|
||||
void RefreshUnlockedChunks();
|
||||
// private:
|
||||
// void RefreshUnlockedChunks();
|
||||
|
||||
private:
|
||||
entt::entity CreateEntity();
|
||||
FactoryError AddEntity(int x, int y, Ref<Archetype> archetype, entt::entity entityID);
|
||||
void InvalidateCachedChunk();
|
||||
// private:
|
||||
// entt::entity CreateEntity();
|
||||
// FactoryError AddEntity(int x, int y, Ref<Archetype> archetype, entt::entity entityID);
|
||||
// void InvalidateCachedChunk();
|
||||
|
||||
public: // UPGRADING
|
||||
FactoryError TryUpgradeEntity(FactoryEntity* entity);
|
||||
FactoryError TryUpgradeEntity(entt::entity entity);
|
||||
FactoryError TryUpgradeEntity(const Vector2i& position);
|
||||
// public: // UPGRADING
|
||||
// FactoryError TryUpgradeEntity(FactoryEntity* entity);
|
||||
// FactoryError TryUpgradeEntity(entt::entity entity);
|
||||
// FactoryError TryUpgradeEntity(const Vector2i& position);
|
||||
|
||||
FactoryError CanUpgradeEntity(entt::entity entity) const;
|
||||
FactoryError CanUpgradeEntity(FactoryEntity* entity) const;
|
||||
FactoryError CanUpgradeEntity(const Vector2i& position);
|
||||
// FactoryError CanUpgradeEntity(entt::entity entity) const;
|
||||
// FactoryError CanUpgradeEntity(FactoryEntity* entity) const;
|
||||
// FactoryError CanUpgradeEntity(const Vector2i& position);
|
||||
|
||||
private:
|
||||
void UpgradeEntity(entt::entity entity);
|
||||
void UpgradeEntity(entt::entity entity, Ref<Archetype> archetype);
|
||||
void SetEntityLevel(entt::entity entity, uint8_t level);
|
||||
void SetEntityLevel(entt::entity entity, Ref<Archetype> archetype, uint8_t level);
|
||||
// private:
|
||||
// void UpgradeEntity(entt::entity entity);
|
||||
// void UpgradeEntity(entt::entity entity, Ref<Archetype> archetype);
|
||||
// void SetEntityLevel(entt::entity entity, uint8_t level);
|
||||
// void SetEntityLevel(entt::entity entity, Ref<Archetype> archetype, uint8_t level);
|
||||
|
||||
public: // QUERY
|
||||
void HighlightUpgradableEntities(TileMapLayer* tilemap) const;
|
||||
FactoryError FindChutePath(Vector<Vector2i>& path, Vector2i startPos, Vector2i endPos) const;
|
||||
Tile const* Raycast(Vector2i startPos, Vector2i endPos) const;
|
||||
bool IsSupport(int x, int y) const;
|
||||
bool IsSupport(entt::entity entity) const;
|
||||
// public: // QUERY
|
||||
// void HighlightUpgradableEntities(TileMapLayer* tilemap) const;
|
||||
// FactoryError FindChutePath(Vector<Vector2i>& path, Vector2i startPos, Vector2i endPos) const;
|
||||
// Tile const* Raycast(Vector2i startPos, Vector2i endPos) const;
|
||||
// bool IsSupport(int x, int y) const;
|
||||
// bool IsSupport(entt::entity entity) const;
|
||||
|
||||
public: // INVENTORY
|
||||
void SetInventory(const Vector<Ref<ItemConfig>>& items);
|
||||
Inventory& GetInventory() { return WorldInventory; }
|
||||
const Inventory& GetInventory() const { return WorldInventory; }
|
||||
Inventory GetWorldInventory(Vector2 position) const;
|
||||
Inventory GetWorldInventory(entt::entity entity) const;
|
||||
// public: // INVENTORY
|
||||
// void SetInventory(const Vector<Ref<ItemConfig>>& items);
|
||||
// Inventory& GetInventory() { return WorldInventory; }
|
||||
// const Inventory& GetInventory() const { return WorldInventory; }
|
||||
// Inventory GetWorldInventory(Vector2 position) const;
|
||||
// Inventory GetWorldInventory(entt::entity entity) const;
|
||||
|
||||
public: // DATA
|
||||
const Recipe& GetRecipe(Ref<RecipeConfig> config) { return WorldInstanceData.GetRecipe(config); }
|
||||
// public: // DATA
|
||||
// const Recipe& GetRecipe(Ref<RecipeConfig> config) { return WorldInstanceData.GetRecipe(config); }
|
||||
|
||||
public: // SUPPORT
|
||||
// FactoryError CanPlaceSupport(int x, int y) const;
|
||||
// FactoryError CanRemoveSupport(int x, int y) const;
|
||||
// void RegisterSupport(int x, int y, Support& support);
|
||||
// void RemoveSupport(int x, int y);
|
||||
// public: // SUPPORT
|
||||
// // FactoryError CanPlaceSupport(int x, int y) const;
|
||||
// // FactoryError CanRemoveSupport(int x, int y) const;
|
||||
// // void RegisterSupport(int x, int y, Support& support);
|
||||
// // void RemoveSupport(int x, int y);
|
||||
|
||||
private:
|
||||
bool SupportCheckerHelper(entt::entity entity) const;
|
||||
uint8_t SupportValueHelper(entt::entity entity) const;
|
||||
uint8_t GetSupportValue(int x, int y) const;
|
||||
bool CheckIfSupportHelper(entt::entity entity, Support& support) const;
|
||||
// private:
|
||||
// bool SupportCheckerHelper(entt::entity entity) const;
|
||||
// uint8_t SupportValueHelper(entt::entity entity) const;
|
||||
// uint8_t GetSupportValue(int x, int y) const;
|
||||
// bool CheckIfSupportHelper(entt::entity entity, Support& support) const;
|
||||
|
||||
void ConnectSupports(Vector2i pos, Support& support, Vector2i direction);
|
||||
// void ConnectSupports(Vector2i pos, Support& support, Vector2i direction);
|
||||
|
||||
private:
|
||||
Mutex WorldAccessMutex;
|
||||
// std::shared_ptr<FactoryCommandQueue> CommandQueue = std::make_shared<FactoryCommandQueue>();
|
||||
// FactoryWorldInterface* Interface;
|
||||
// private:
|
||||
// Mutex WorldAccessMutex;
|
||||
// // std::shared_ptr<FactoryCommandQueue> CommandQueue = std::make_shared<FactoryCommandQueue>();
|
||||
// // FactoryWorldInterface* Interface;
|
||||
|
||||
ChunkCollection Chunks{};
|
||||
// ChunkCollection Chunks{};
|
||||
|
||||
entt::registry Registry{};
|
||||
// entt::registry Registry{};
|
||||
|
||||
Ref<FactoryWorldSettings> WorldSettings{};
|
||||
// Ref<FactoryWorldSettings> WorldSettings{};
|
||||
|
||||
int32_t Seed{};
|
||||
int32_t LastDrawnFrame{};
|
||||
// int32_t Seed{};
|
||||
// int32_t LastDrawnFrame{};
|
||||
|
||||
Inventory64 WorldInventory;
|
||||
WorldData WorldInstanceData;
|
||||
Vector<ChunkKey> UnlockedChunks{ ChunkKey{} };
|
||||
Vector<ChunkUnlock> UnlockableChunks{};
|
||||
};
|
||||
// Inventory64 WorldInventory;
|
||||
// WorldData WorldInstanceData;
|
||||
// Vector<ChunkKey> UnlockedChunks{ ChunkKey{} };
|
||||
// Vector<ChunkUnlock> UnlockableChunks{};
|
||||
// };
|
||||
@@ -1,95 +1,95 @@
|
||||
#pragma once
|
||||
// #pragma once
|
||||
|
||||
#include "Data/WorldSettings.h"
|
||||
#include "Chunk.h"
|
||||
// #include "Data/WorldSettings.h"
|
||||
// #include "Chunk.h"
|
||||
|
||||
class FactoryWorld;
|
||||
// class FactoryWorld;
|
||||
|
||||
// class WorldGenerator final
|
||||
// // class WorldGenerator final
|
||||
// // {
|
||||
// // public:
|
||||
|
||||
|
||||
// // public:
|
||||
// // WorldGenerator() = default;
|
||||
// // WorldGenerator(Ref<FactoryWorldSettings> settings, int32_t seed);
|
||||
|
||||
// // public:
|
||||
// // // bool GenerateChunk(ChunkKey chunkKey, Chunk& chunk) const;
|
||||
// // // bool GenerateChunk(ChunkKey chunkKey, Chunk& chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer = {}) const;
|
||||
|
||||
// // // Vector<SpawnedEntities> SpawnEntities(ChunkKey chunkKey, Chunk& chunk, const std::vector<EntityTile>& persistantEntities = {}) const;
|
||||
// // // Vector<SpawnedEntities> SpawnEntities(ChunkKey chunkKey, Chunk& chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer = {}, const std::vector<EntityTile>& persistantEntities = {}) const;
|
||||
|
||||
// // // std::unique_ptr<CreatedVisualsChunk> CreateChunkVisuals(ChunkKey chunkKey, Chunk& chunk);
|
||||
|
||||
// // public:
|
||||
// // void ThreadedGenerateChunk(ChunkKey chunkKey, std::function<void(ChunkData&&)> callback, const std::vector<EntityTile>& persistantEntities = {});
|
||||
|
||||
// // private:
|
||||
|
||||
// // public:
|
||||
// // Ref<FactoryWorldSettings> Settings{};
|
||||
// // WorldGraph Graph{};
|
||||
// // int32_t Seed{};
|
||||
// // };
|
||||
|
||||
// class ChunkGenerator final
|
||||
// {
|
||||
// public:
|
||||
|
||||
|
||||
// public:
|
||||
// WorldGenerator() = default;
|
||||
// WorldGenerator(Ref<FactoryWorldSettings> settings, int32_t seed);
|
||||
|
||||
// public:
|
||||
// // bool GenerateChunk(ChunkKey chunkKey, Chunk& chunk) const;
|
||||
// // bool GenerateChunk(ChunkKey chunkKey, Chunk& chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer = {}) const;
|
||||
|
||||
// // Vector<SpawnedEntities> SpawnEntities(ChunkKey chunkKey, Chunk& chunk, const std::vector<EntityTile>& persistantEntities = {}) const;
|
||||
// // Vector<SpawnedEntities> SpawnEntities(ChunkKey chunkKey, Chunk& chunk, Ref<LayerConfig> layer, Ref<LayerConfig> nextLayer = {}, const std::vector<EntityTile>& persistantEntities = {}) const;
|
||||
|
||||
// // std::unique_ptr<CreatedVisualsChunk> CreateChunkVisuals(ChunkKey chunkKey, Chunk& chunk);
|
||||
|
||||
// public:
|
||||
// void ThreadedGenerateChunk(ChunkKey chunkKey, std::function<void(ChunkData&&)> callback, const std::vector<EntityTile>& persistantEntities = {});
|
||||
|
||||
// private:
|
||||
|
||||
// public:
|
||||
// Ref<FactoryWorldSettings> Settings{};
|
||||
// WorldGraph Graph{};
|
||||
// int32_t Seed{};
|
||||
// struct SpawnedEntities
|
||||
// {
|
||||
// Ref<Archetype> Archetype{};
|
||||
// Vector2i SpawnPosition{};
|
||||
// Vector<Vector2i> ClaimedPositions{};
|
||||
// };
|
||||
|
||||
class ChunkGenerator final
|
||||
{
|
||||
public:
|
||||
struct SpawnedEntities
|
||||
{
|
||||
Ref<Archetype> Archetype{};
|
||||
Vector2i SpawnPosition{};
|
||||
Vector<Vector2i> ClaimedPositions{};
|
||||
};
|
||||
// struct CreatedVisualsTile
|
||||
// {
|
||||
// CreatedVisualsTile() = default;
|
||||
// CreatedVisualsTile(uint16_t atlasCoordinateX, uint16_t atlasCoordinateY, uint16_t atlasIndex) : AtlasCoordinateX{ atlasCoordinateX }, AtlasCoordinateY{ atlasCoordinateY }, AtlasIndex{ atlasIndex } {};
|
||||
|
||||
struct CreatedVisualsTile
|
||||
{
|
||||
CreatedVisualsTile() = default;
|
||||
CreatedVisualsTile(uint16_t atlasCoordinateX, uint16_t atlasCoordinateY, uint16_t atlasIndex) : AtlasCoordinateX{ atlasCoordinateX }, AtlasCoordinateY{ atlasCoordinateY }, AtlasIndex{ atlasIndex } {};
|
||||
|
||||
uint16_t AtlasCoordinateX{};
|
||||
uint16_t AtlasCoordinateY{};
|
||||
uint16_t AtlasIndex{};
|
||||
};
|
||||
// uint16_t AtlasCoordinateX{};
|
||||
// uint16_t AtlasCoordinateY{};
|
||||
// uint16_t AtlasIndex{};
|
||||
// };
|
||||
|
||||
|
||||
typedef std::array<CreatedVisualsTile, Chunk::TotalChunkTiles> CreatedVisualsChunk;
|
||||
typedef std::array<int8_t, Chunk::TotalChunkTiles> ChunkShadowValues;
|
||||
// typedef std::array<CreatedVisualsTile, Chunk::TotalChunkTiles> CreatedVisualsChunk;
|
||||
// typedef std::array<int8_t, Chunk::TotalChunkTiles> ChunkShadowValues;
|
||||
|
||||
typedef std::function<void(std::unique_ptr<Chunk>&&)> CreatedChunkCallback;
|
||||
typedef std::function<void(const Vector<SpawnedEntities>&)> SpawnedEntitiesCallback;
|
||||
typedef std::function<void(CreatedVisualsChunk*)> VisualizedChunkCallback;
|
||||
typedef std::function<void(ChunkShadowValues*)> ShadowsCallback;
|
||||
// typedef std::function<void(std::unique_ptr<Chunk>&&)> CreatedChunkCallback;
|
||||
// typedef std::function<void(const Vector<SpawnedEntities>&)> SpawnedEntitiesCallback;
|
||||
// typedef std::function<void(CreatedVisualsChunk*)> VisualizedChunkCallback;
|
||||
// typedef std::function<void(ChunkShadowValues*)> ShadowsCallback;
|
||||
|
||||
ChunkGenerator() = default;
|
||||
private:
|
||||
ChunkGenerator(Ref<FactoryWorldSettings> settings, ChunkKey chunk, int32_t seed) : Settings{ settings }, ChunkInfo{ chunk }, Seed{ seed } {};
|
||||
// ChunkGenerator() = default;
|
||||
// private:
|
||||
// ChunkGenerator(Ref<FactoryWorldSettings> settings, ChunkKey chunk, int32_t seed) : Settings{ settings }, ChunkInfo{ chunk }, Seed{ seed } {};
|
||||
|
||||
public:
|
||||
static void GenerateChunk(Ref<FactoryWorldSettings> settings, ChunkKey chunkInfo, int seed, CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback = {}, VisualizedChunkCallback visualsCallback = {}, ShadowsCallback shadowsCallback = {});
|
||||
std::unique_ptr<Chunk> GenerateChunk();
|
||||
// public:
|
||||
// static void GenerateChunk(Ref<FactoryWorldSettings> settings, ChunkKey chunkInfo, int seed, CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback = {}, VisualizedChunkCallback visualsCallback = {}, ShadowsCallback shadowsCallback = {});
|
||||
// std::unique_ptr<Chunk> GenerateChunk();
|
||||
|
||||
private:
|
||||
static void GenerateChunk(void* pData);
|
||||
void GenerateChunkInternal(CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback, VisualizedChunkCallback visualsCallback, ShadowsCallback shadowsCallback);
|
||||
// private:
|
||||
// static void GenerateChunk(void* pData);
|
||||
// void GenerateChunkInternal(CreatedChunkCallback chunkCallback, SpawnedEntitiesCallback entitiesCallback, VisualizedChunkCallback visualsCallback, ShadowsCallback shadowsCallback);
|
||||
|
||||
void GenerateChunkTiles() const;
|
||||
Vector<SpawnedEntities> SpawnEntities(const std::vector<EntityTile>& persistantEntities = {}) const;
|
||||
std::unique_ptr<CreatedVisualsChunk> CreateVisuals();
|
||||
std::unique_ptr<ChunkShadowValues> CascadeShadows();
|
||||
// void GenerateChunkTiles() const;
|
||||
// Vector<SpawnedEntities> SpawnEntities(const std::vector<EntityTile>& persistantEntities = {}) const;
|
||||
// std::unique_ptr<CreatedVisualsChunk> CreateVisuals();
|
||||
// std::unique_ptr<ChunkShadowValues> CascadeShadows();
|
||||
|
||||
Pair<Ref<LayerConfig>, Ref<LayerConfig>> GetLayers() const;
|
||||
void FillChunkCollection(int relativeX, int relativeY, ChunkCollection& collection) const;
|
||||
void CascadeShadows_Recursive(std::array<int8_t, WorldNodeParameters::PaddedChunkSize>& values, int posX, int posY, int value);
|
||||
// Pair<Ref<LayerConfig>, Ref<LayerConfig>> GetLayers() const;
|
||||
// void FillChunkCollection(int relativeX, int relativeY, ChunkCollection& collection) const;
|
||||
// void CascadeShadows_Recursive(std::array<int8_t, WorldNodeParameters::PaddedChunkSize>& values, int posX, int posY, int value);
|
||||
|
||||
Tile GetTile(int x, int y) const;
|
||||
bool InBounds(int x, int y) const;
|
||||
// Tile GetTile(int x, int y) const;
|
||||
// bool InBounds(int x, int y) const;
|
||||
|
||||
private:
|
||||
Ref<FactoryWorldSettings> Settings{};
|
||||
ChunkKey ChunkInfo{};
|
||||
int32_t Seed{};
|
||||
std::unique_ptr<WorldNodeParameters::TileArray> TileArray{};
|
||||
};
|
||||
// private:
|
||||
// Ref<FactoryWorldSettings> Settings{};
|
||||
// ChunkKey ChunkInfo{};
|
||||
// int32_t Seed{};
|
||||
// std::unique_ptr<WorldNodeParameters::TileArray> TileArray{};
|
||||
// };
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
#include "Item.hpp"
|
||||
|
||||
struct alignas(4) ItemFilter final
|
||||
{
|
||||
uint16_t FilterItem0;
|
||||
uint16_t FilterItem1;
|
||||
uint16_t FilterItem2;
|
||||
//Item::ItemFlags FilterFlags;
|
||||
// struct alignas(4) ItemFilter final
|
||||
// {
|
||||
// uint16_t FilterItem0;
|
||||
// uint16_t FilterItem1;
|
||||
// uint16_t FilterItem2;
|
||||
// //Item::ItemFlags FilterFlags;
|
||||
|
||||
public:
|
||||
inline bool ApplyFilter(Item item)
|
||||
{
|
||||
return //(item.Flags & FilterFlags) &&
|
||||
(FilterItem0 == 0 || FilterItem0 == item.ItemID) &&
|
||||
(FilterItem1 == 0 || FilterItem1 == item.ItemID) &&
|
||||
(FilterItem2 == 0 || FilterItem2 == item.ItemID);
|
||||
}
|
||||
};
|
||||
// public:
|
||||
// inline bool ApplyFilter(Item item)
|
||||
// {
|
||||
// return //(item.Flags & FilterFlags) &&
|
||||
// (FilterItem0 == 0 || FilterItem0 == item.ItemID) &&
|
||||
// (FilterItem1 == 0 || FilterItem1 == item.ItemID) &&
|
||||
// (FilterItem2 == 0 || FilterItem2 == item.ItemID);
|
||||
// }
|
||||
// };
|
||||
48
include/Types/Grid8x8.h
Normal file
48
include/Types/Grid8x8.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 8x8 bitset grid. Each bit represents a cell: bit index = (y << 3) | x.
|
||||
struct Grid8x8 final
|
||||
{
|
||||
struct Cell
|
||||
{
|
||||
int X, Y;
|
||||
};
|
||||
|
||||
class Iterator
|
||||
{
|
||||
uint64_t Remaining;
|
||||
|
||||
public:
|
||||
explicit Iterator(uint64_t bits) : Remaining(bits) {}
|
||||
|
||||
bool operator!=(const Iterator& other) const { return Remaining != other.Remaining; }
|
||||
|
||||
Cell operator*() const
|
||||
{
|
||||
int pos = __builtin_ctzll(Remaining);
|
||||
return { pos & 7, pos >> 3 };
|
||||
}
|
||||
|
||||
Iterator& operator++()
|
||||
{
|
||||
Remaining &= Remaining - 1; // clear lowest set bit
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
Iterator begin() const { return Iterator(Bits); }
|
||||
Iterator end() const { return Iterator(0); }
|
||||
|
||||
// Number of filled cells.
|
||||
int Count() const { return __builtin_popcountll(Bits); }
|
||||
|
||||
bool Get(int x, int y) const { return (Bits >> ((y << 3) | x)) & 1; }
|
||||
void Set(int x, int y) { Bits |= uint64_t(1) << ((y << 3) | x); }
|
||||
void Clear(int x, int y) { Bits &= ~(uint64_t(1) << ((y << 3) | x)); }
|
||||
|
||||
uint64_t Bits = 0;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Grid8x8) == 8);
|
||||
@@ -1,60 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "scene/resources/texture.h"
|
||||
#include "modules/factory/thirdParty/EnTT/entity/entity.hpp"
|
||||
#include "modules/factory/include/Util/Helpers.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// If tile data uses all 4 bytes, it is an entity,
|
||||
// if it uses only 2 bytes it is a tile
|
||||
// if it is 0, it is air
|
||||
// struct Tile final
|
||||
// {
|
||||
// private:
|
||||
// union TileData
|
||||
// {
|
||||
// uint32_t Data;
|
||||
// entt::entity Entity;
|
||||
// uint16_t TileID;
|
||||
// };
|
||||
// static_assert(sizeof(TileData) == sizeof(uint32_t));
|
||||
// TileData Data{};
|
||||
|
||||
// static constexpr uint32_t TileMask = 0x80000000;
|
||||
|
||||
// public:
|
||||
// Tile() = default;
|
||||
// Tile(uint16_t id) { Data.Data = id | TileMask; }
|
||||
// Tile(entt::entity entity) { DEV_ASSERT(IsValidEntity(entity)); Data.Entity = entity; }
|
||||
|
||||
// public:
|
||||
// constexpr bool IsAir() const { return Data.Data == 0; }
|
||||
// constexpr bool IsTile() const { return (Data.Data & TileMask) != 0; }
|
||||
// constexpr bool IsEntity() const { return !IsAir() && !IsTile(); }
|
||||
// static constexpr bool IsValidEntity(entt::entity entity) { return (((uint32_t)entity) & TileMask) == 0; }
|
||||
|
||||
// public:
|
||||
// entt::entity GetEntity() const { DEV_ASSERT(IsEntity()); return Data.Entity; }
|
||||
// uint16_t GetTileID() const { DEV_ASSERT(IsTile()); return Data.TileID; }
|
||||
// };
|
||||
|
||||
enum TILE_TYPE : uint8_t
|
||||
enum class TileType : uint8_t
|
||||
{
|
||||
TILE_AIR,
|
||||
TILE_FILLER,
|
||||
TILE_LIQUID,
|
||||
TILE_ORE,
|
||||
TILE_NPC,
|
||||
TILE_PLANT,
|
||||
TILE_MAX,
|
||||
Air,
|
||||
Filler,
|
||||
Liquid,
|
||||
Ore,
|
||||
NPC,
|
||||
Plant,
|
||||
MAX,
|
||||
|
||||
TILE_NONE = 0b111,
|
||||
NONE = 0b111,
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(TILE_TYPE);
|
||||
static_assert(TILE_MAX <= TILE_NONE);
|
||||
const char* TileTypeEnumString = "Air,Filler,Liquid,Ore,Npc,Plant,,None";
|
||||
static_assert(static_cast<uint8_t>(TileType::MAX) <= static_cast<uint8_t>(TileType::NONE));
|
||||
|
||||
struct Tile final
|
||||
{
|
||||
@@ -80,25 +41,25 @@ public:
|
||||
constexpr bool HasEntity() const { return Data & MaskEntity; }
|
||||
|
||||
constexpr uint16_t GetID() const { return Data & MaskID; }
|
||||
constexpr bool IsAir() const { return GetType() == TILE_AIR; }
|
||||
constexpr bool IsFiller() const { return GetType() == TILE_FILLER; }
|
||||
constexpr bool IsLiquid() const { return GetType() == TILE_LIQUID; }
|
||||
constexpr bool IsOre() const { return GetType() == TILE_ORE; }
|
||||
constexpr bool IsPlant() const { return GetType() == TILE_PLANT; }
|
||||
constexpr bool IsNPC() const { return GetType() == TILE_NPC; }
|
||||
constexpr bool IsAir() const { return GetType() == TileType::Air; }
|
||||
constexpr bool IsFiller() const { return GetType() == TileType::Filler; }
|
||||
constexpr bool IsLiquid() const { return GetType() == TileType::Liquid; }
|
||||
constexpr bool IsOre() const { return GetType() == TileType::Ore; }
|
||||
constexpr bool IsPlant() const { return GetType() == TileType::Plant; }
|
||||
constexpr bool IsNPC() const { return GetType() == TileType::NPC; }
|
||||
|
||||
constexpr TILE_TYPE GetType() const { return static_cast<TILE_TYPE>((Data & MaskType) >> BytesID); }
|
||||
constexpr bool IsType(TILE_TYPE type) const { return GetType() == type; }
|
||||
constexpr TileType GetType() const { return static_cast<TileType>((Data & MaskType) >> BytesID); }
|
||||
constexpr bool IsType(TileType type) const { return GetType() == type; }
|
||||
|
||||
void SetID(uint16_t id) { Data = (Data & ~MaskID) | (id & MaskID); }
|
||||
void SetAir() { SetType(TILE_AIR); }
|
||||
void SetFiller() { SetType(TILE_FILLER); }
|
||||
void SetLiquid() { SetType(TILE_LIQUID); }
|
||||
void SetOre() { SetType(TILE_ORE); }
|
||||
void SetPlant() { SetType(TILE_PLANT); }
|
||||
void SetNPC() { SetType(TILE_NPC); }
|
||||
void SetAir() { SetType(TileType::Air); }
|
||||
void SetFiller() { SetType(TileType::Filler); }
|
||||
void SetLiquid() { SetType(TileType::Liquid); }
|
||||
void SetOre() { SetType(TileType::Ore); }
|
||||
void SetPlant() { SetType(TileType::Plant); }
|
||||
void SetNPC() { SetType(TileType::NPC); }
|
||||
|
||||
void SetType(TILE_TYPE type) { Data = (Data & ~MaskType) | (type << BytesID); }
|
||||
void SetType(TileType type) { Data = (Data & ~MaskType) | (static_cast<uint16_t>(type) << BytesID); }
|
||||
|
||||
constexpr uint16_t AsInt() const { return Data; }
|
||||
constexpr bool IsValid() const { return Data == MaskID; }
|
||||
@@ -111,66 +72,3 @@ inline constexpr bool operator==(Tile lhs, Tile rhs) { return lhs.AsInt() == rhs
|
||||
inline constexpr bool operator!=(Tile lhs, Tile rhs) { return lhs.AsInt() != rhs.AsInt(); }
|
||||
|
||||
static_assert(sizeof(Tile) == 2);
|
||||
|
||||
class TextureWeight final : public Resource
|
||||
{
|
||||
GDCLASS(TextureWeight, Resource);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
Ref<Texture2D> GetTexture() const { return Texture; }
|
||||
uint16_t GetWeight() const { return Weight; }
|
||||
|
||||
void SetTexture(Ref<Texture2D> texture) { Texture = texture; }
|
||||
void SetWeight(uint16_t weight) { Weight = weight; }
|
||||
|
||||
public:
|
||||
Ref<Texture2D> Texture{};
|
||||
uint16_t Weight{1};
|
||||
uint16_t AtlasIndex{};
|
||||
uint16_t AtlasX{};
|
||||
uint16_t AtlasY{};
|
||||
};
|
||||
|
||||
class TileConfig;
|
||||
|
||||
class TileTransitionConfig final : public Resource
|
||||
{
|
||||
GDCLASS(TileTransitionConfig, Resource);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
auto GetNeighbor() const { return Neighbor; }
|
||||
auto GetPossibleTextures() const { return VectorToTypedArray(PossibleTextures); }
|
||||
|
||||
void SetNeighbor(Ref<TileConfig> neighbor) { Neighbor = neighbor; }
|
||||
void SetPossibleTextures(TypedArray<TextureWeight> textures) { PossibleTextures = TypedArrayToVector(textures); }
|
||||
|
||||
public:
|
||||
Ref<TileConfig> Neighbor;
|
||||
Vector<Ref<TextureWeight>> PossibleTextures;
|
||||
};
|
||||
|
||||
class TileConfig final : public Resource
|
||||
{
|
||||
GDCLASS(TileConfig, Resource);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
uint16_t GetID() const { return TileData.GetID(); }
|
||||
TILE_TYPE GetType() const { return TileData.GetType(); }
|
||||
auto GetTextures() const { return VectorToTypedArray(PossibleTextures); }
|
||||
auto GetTransitions() const { return VectorToTypedArray(NeighborTransitions); }
|
||||
auto GetLightResistance() const { return VectorToTypedArray(NeighborTransitions); }
|
||||
|
||||
void SetID(uint16_t id) { TileData.SetID(id); }
|
||||
void SetTextures(TypedArray<TextureWeight> val) { PossibleTextures = TypedArrayToVector(val); }
|
||||
void SetTransitions(TypedArray<TileTransitionConfig> val) { NeighborTransitions = TypedArrayToVector(val); }
|
||||
void SetType(TILE_TYPE type) { TileData.SetType(type); }
|
||||
void SetLightResistance(int resistance) { LightResistance = resistance; }
|
||||
|
||||
public:
|
||||
Tile TileData{};
|
||||
// Vector<Ref<TextureWeight>> PossibleTextures;
|
||||
// Vector<Ref<TileTransitionConfig>> NeighborTransitions;
|
||||
int LightResistance{};
|
||||
};
|
||||
@@ -1,30 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldGraphVisualNode.h"
|
||||
#include "WorldGraphNode.h"
|
||||
#include "flecs.h"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
// WorldGraph compiles a Flecs entity graph into a flat memory-pooled runtime
|
||||
// graph of WorldNodeBase* objects that can be evaluated per-tile.
|
||||
//
|
||||
// Usage:
|
||||
// auto graph = WorldGraph::Compile(world, world.lookup("MyGraph"));
|
||||
// NodeValue result = graph.Execute(params);
|
||||
|
||||
class WorldGraph final
|
||||
{
|
||||
public:
|
||||
WorldGraph() = default;
|
||||
WorldGraph(const Vector<Ref<WorldGraphVisualNodeBase>>& nodes);
|
||||
|
||||
WorldGraph(const WorldGraph& other);
|
||||
WorldGraph(WorldGraph&& other) noexcept = default;
|
||||
// Non-copyable (raw pointers into owned buffer); movable.
|
||||
WorldGraph(const WorldGraph&) = delete;
|
||||
WorldGraph& operator=(const WorldGraph&) = delete;
|
||||
WorldGraph(WorldGraph&&) noexcept = default;
|
||||
WorldGraph& operator=(WorldGraph&&) noexcept = default;
|
||||
|
||||
WorldGraph& operator=(const WorldGraph& other);
|
||||
WorldGraph& operator=(WorldGraph&& other) noexcept = default;
|
||||
// Compile from Flecs world. graphRoot is the parent entity whose children
|
||||
// are the node entities. Returns an invalid WorldGraph if compilation fails.
|
||||
static WorldGraph Compile(flecs::world& world, flecs::entity graphRoot);
|
||||
|
||||
public:
|
||||
Variant Execute(Ref<WorldGraphVisualNodeBase> node, const WorldNodeParameters& params) const;
|
||||
WorldNodeBase* GetNode(Ref<WorldGraphVisualNodeBase> node) const;
|
||||
// Evaluate from the designated output node (VisualNodeOutput tag).
|
||||
NodeValue Execute(const WorldNodeParameters& params) const;
|
||||
|
||||
bool IsValid() const { return RootNode != nullptr; }
|
||||
|
||||
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{};
|
||||
uint32_t MemorySize{};
|
||||
std::unique_ptr<uint8_t[]> CompiledData{};
|
||||
WorldNodeBase* RootNode{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include "modules/factory/include/Util/Span.h"
|
||||
#include "Util/Span.h"
|
||||
|
||||
struct WorldGraphAllocatorBase
|
||||
{
|
||||
|
||||
@@ -5,77 +5,58 @@
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
#include "WorldGraphAllocator.h"
|
||||
#include "Util/FastNoiseLite.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "modules/factory/include/Core/Chunk.h"
|
||||
#include "Core/Chunk.h"
|
||||
#include "Components/Misc.hpp"
|
||||
|
||||
// // 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) {}
|
||||
// ─── Node value type ─────────────────────────────────────────────────────────
|
||||
|
||||
// public:
|
||||
// void SetFloat(float val) { Data = reinterpret_cast<uint32_t&>(val) | 0b1u; }
|
||||
// void SetBool(bool val) { Data = (val << 1) & (~0b1u); }
|
||||
enum class NodeValueType : uint8_t { Float, Bool };
|
||||
|
||||
// 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
|
||||
struct NodeValue
|
||||
{
|
||||
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;
|
||||
union { float f; bool b; } data{};
|
||||
NodeValueType type = NodeValueType::Float;
|
||||
|
||||
NodeValue() = default;
|
||||
NodeValue(float v) : type(NodeValueType::Float) { data.f = v; }
|
||||
NodeValue(bool v) : type(NodeValueType::Bool) { data.b = v; }
|
||||
|
||||
template<typename T> T get() const;
|
||||
};
|
||||
|
||||
template<> inline float NodeValue::get<float>() const { return data.f; }
|
||||
template<> inline bool NodeValue::get<bool>() const { return data.b; }
|
||||
|
||||
// ─── Parameters passed per-tile during evaluation ────────────────────────────
|
||||
|
||||
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;
|
||||
using TileArray = std::array<Tile, PaddedChunkSize>;
|
||||
|
||||
int X{ };
|
||||
int Y{ };
|
||||
int Seed{ };
|
||||
float FinalValueSubstract{ };
|
||||
int X{};
|
||||
int Y{};
|
||||
int Seed{};
|
||||
float FinalValueSubstract{};
|
||||
|
||||
ChunkKey ChunkInfo{ };
|
||||
TileArray* GeneratedTiles{ };
|
||||
ChunkKey ChunkInfo{};
|
||||
TileArray* GeneratedTiles{};
|
||||
|
||||
Tile GetTile(int x, int y) const
|
||||
{
|
||||
auto bounds = GetGenerationBounds();
|
||||
if (unlikely(!bounds.has_point(Vector2i{x, y})))
|
||||
if (!bounds.HasPoint(x, y))
|
||||
return {};
|
||||
// DEV_ASSERT(bounds.has_point(Vector2i{x, y}));
|
||||
|
||||
return (*GeneratedTiles)[(y - bounds.position.y) * PaddedChunkSide + (x - bounds.position.x)];
|
||||
return (*GeneratedTiles)[(y - bounds.Min.Y) * PaddedChunkSide + (x - bounds.Min.X)];
|
||||
}
|
||||
|
||||
static int GetArrayIndex(int x, int y)
|
||||
@@ -83,53 +64,67 @@ struct WorldNodeParameters
|
||||
return (y + MaxQueryOffset) * PaddedChunkSide + (x + MaxQueryOffset);
|
||||
}
|
||||
|
||||
Rect2i GetGenerationBounds() const
|
||||
Bounds GetGenerationBounds() const
|
||||
{
|
||||
return ChunkInfo.GetBounds().grow(MaxQueryOffset);
|
||||
return ChunkInfo.GetBounds().Grow(MaxQueryOffset);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct TtoVariant
|
||||
// ─── Base interface ───────────────────────────────────────────────────────────
|
||||
|
||||
struct alignas(void*) WorldNodeBase
|
||||
{
|
||||
typedef Variant TVariant;
|
||||
virtual NodeValue Evaluate(const WorldNodeParameters& params) const = 0;
|
||||
virtual NodeValueType GetReturnType() const = 0;
|
||||
virtual std::vector<NodeValueType> GetInputTypes() const = 0;
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual void Allocate(WorldGraphAllocatorBase* allocator) const = 0;
|
||||
virtual void SetInput(int index, WorldNodeBase* input) = 0;
|
||||
};
|
||||
|
||||
template <typename Return, typename ... Inputs>
|
||||
// ─── Typed templated base ─────────────────────────────────────────────────────
|
||||
|
||||
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
|
||||
virtual NodeValueType GetReturnType() const override
|
||||
{
|
||||
return { Variant::get_type_t<Inputs>()... };
|
||||
};
|
||||
if constexpr (std::is_same_v<Return, float>) return NodeValueType::Float;
|
||||
else return NodeValueType::Bool;
|
||||
}
|
||||
|
||||
virtual std::vector<NodeValueType> GetInputTypes() const override
|
||||
{
|
||||
return { (std::is_same_v<Inputs, float> ? NodeValueType::Float : NodeValueType::Bool)... };
|
||||
}
|
||||
|
||||
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);
|
||||
for (auto* input : InputNodes)
|
||||
if (!input) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto EvaluateFunctor = [this](typename TtoVariant<Inputs>::TVariant... args) -> Variant
|
||||
virtual void Allocate(WorldGraphAllocatorBase* allocator) const override
|
||||
{
|
||||
return EvaluateT(args.get_unsafe_t<Inputs>()...);
|
||||
};
|
||||
allocator->Allocate(*this);
|
||||
}
|
||||
|
||||
return std::apply(EvaluateFunctor, results);
|
||||
virtual NodeValue Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
// Collect input results into an array
|
||||
auto inputValues = [&]<size_t... I>(std::index_sequence<I...>) {
|
||||
return std::array<NodeValue, sizeof...(Inputs)>{ InputNodes[I]->Evaluate(params)... };
|
||||
}(std::make_index_sequence<sizeof...(Inputs)>{});
|
||||
|
||||
// Unpack typed values and call EvaluateT
|
||||
return [&]<size_t... I>(std::index_sequence<I...>) -> NodeValue {
|
||||
return NodeValue(EvaluateT(
|
||||
inputValues[I].template get<std::tuple_element_t<I, std::tuple<Inputs...>>>()...
|
||||
));
|
||||
}(std::make_index_sequence<sizeof...(Inputs)>{});
|
||||
}
|
||||
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
@@ -137,529 +132,215 @@ struct WorldNodeTemplated : public WorldNodeBase
|
||||
InputNodes[index] = input;
|
||||
}
|
||||
|
||||
|
||||
virtual Return EvaluateT(Inputs...) const = 0;
|
||||
};
|
||||
|
||||
struct WorldNode_Add : public WorldNodeTemplated<float, float, float>
|
||||
// ─── Math nodes ──────────────────────────────────────────────────────────────
|
||||
|
||||
struct WorldNode_Add : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return a+b;} };
|
||||
struct WorldNode_Subtract : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return a-b;} };
|
||||
struct WorldNode_Multiply : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return a*b;} };
|
||||
struct WorldNode_Divide : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return a/b;} };
|
||||
struct WorldNode_Modulo : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return std::fmod(a,b);} };
|
||||
struct WorldNode_Pow : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return std::pow(a,b);} };
|
||||
struct WorldNode_Max : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return std::max(a,b);} };
|
||||
struct WorldNode_Min : WorldNodeTemplated<float,float,float> { float EvaluateT(float a,float b)const override{return std::min(a,b);} };
|
||||
|
||||
struct WorldNode_Negate : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return -v;} };
|
||||
struct WorldNode_Abs : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::abs(v);} };
|
||||
struct WorldNode_Ceil : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::ceil(v);} };
|
||||
struct WorldNode_Floor : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::floor(v);} };
|
||||
struct WorldNode_Sin : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::sin(v);} };
|
||||
struct WorldNode_Cos : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::cos(v);} };
|
||||
struct WorldNode_Tan : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::tan(v);} };
|
||||
struct WorldNode_Exp : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::exp(v);} };
|
||||
struct WorldNode_Log : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::log(v);} };
|
||||
struct WorldNode_Square : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return v*v;} };
|
||||
struct WorldNode_Round : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return std::round(v);} };
|
||||
struct WorldNode_OneMinus : WorldNodeTemplated<float,float> { float EvaluateT(float v)const override{return 1.f-v;} };
|
||||
|
||||
struct WorldNode_Lerp : WorldNodeTemplated<float,float,float,float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 + v1;
|
||||
}
|
||||
float EvaluateT(float from, float to, float t) const override { return std::lerp(from, to, t); }
|
||||
};
|
||||
struct WorldNode_Clamp : WorldNodeTemplated<float,float,float,float>
|
||||
{
|
||||
float EvaluateT(float v, float lo, float hi) const override { return std::clamp(v, lo, hi); }
|
||||
};
|
||||
|
||||
struct WorldNode_Subtract : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 - v1;
|
||||
}
|
||||
};
|
||||
// ─── Comparison nodes ─────────────────────────────────────────────────────────
|
||||
|
||||
struct WorldNode_Multiply : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 * v1;
|
||||
}
|
||||
};
|
||||
struct WorldNode_Equal : WorldNodeTemplated<bool,float,float> { bool EvaluateT(float a,float b)const override{return a==b;} };
|
||||
struct WorldNode_Smaller : WorldNodeTemplated<bool,float,float> { bool EvaluateT(float a,float b)const override{return a<b;} };
|
||||
struct WorldNode_Greater : WorldNodeTemplated<bool,float,float> { bool EvaluateT(float a,float b)const override{return a>b;} };
|
||||
struct WorldNode_SmallerEqual : WorldNodeTemplated<bool,float,float> { bool EvaluateT(float a,float b)const override{return a<=b;} };
|
||||
struct WorldNode_GreaterEqual : WorldNodeTemplated<bool,float,float> { bool EvaluateT(float a,float b)const override{return a>=b;} };
|
||||
|
||||
struct WorldNode_Divide : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return v0 / v1;
|
||||
}
|
||||
};
|
||||
// ─── Logic nodes ──────────────────────────────────────────────────────────────
|
||||
|
||||
struct WorldNode_Modulo : public WorldNodeTemplated<float, float, float>
|
||||
{
|
||||
virtual float EvaluateT(float v0, float v1) const override
|
||||
{
|
||||
return fmod(v0, v1);
|
||||
}
|
||||
};
|
||||
struct WorldNode_And : WorldNodeTemplated<bool,bool,bool> { bool EvaluateT(bool a,bool b)const override{return a&&b;} };
|
||||
struct WorldNode_Or : WorldNodeTemplated<bool,bool,bool> { bool EvaluateT(bool a,bool b)const override{return a||b;} };
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
// ─── Branch (if/else) ─────────────────────────────────────────────────────────
|
||||
|
||||
struct WorldNode_Branch : public WorldNodeBase
|
||||
{
|
||||
WorldNodeBase* InputBool{};
|
||||
WorldNodeBase* InputTrue{};
|
||||
WorldNodeBase* InputFalse{};
|
||||
WorldNodeBase* InputBool {};
|
||||
WorldNodeBase* InputTrue {};
|
||||
WorldNodeBase* InputFalse {};
|
||||
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
virtual NodeValue Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
bool condition = InputBool->Evaluate(params).get_unsafe_bool();
|
||||
bool condition = InputBool->Evaluate(params).get<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 NodeValueType GetReturnType() const override { return NodeValueType::Float; }
|
||||
virtual std::vector<NodeValueType> GetInputTypes() const override { return { NodeValueType::Bool, NodeValueType::Float, NodeValueType::Float }; }
|
||||
virtual bool IsValid() const override { return InputBool && InputTrue && InputFalse; }
|
||||
virtual void Allocate(WorldGraphAllocatorBase* a) const override { a->Allocate(*this); }
|
||||
virtual void SetInput(int index, WorldNodeBase* input) override
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: InputBool = input;
|
||||
case 1: InputTrue = input;
|
||||
case 2: InputFalse = input;
|
||||
case 0: InputBool = input; break;
|
||||
case 1: InputTrue = input; break;
|
||||
case 2: InputFalse = input; break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ─── Constant ─────────────────────────────────────────────────────────────────
|
||||
|
||||
struct WorldNode_Constant : public WorldNodeBase
|
||||
{
|
||||
float Value{};
|
||||
|
||||
virtual NodeValue Evaluate(const WorldNodeParameters&) const override { return NodeValue(Value); }
|
||||
virtual NodeValueType GetReturnType() const override { return NodeValueType::Float; }
|
||||
virtual std::vector<NodeValueType> GetInputTypes() const override { return {}; }
|
||||
virtual bool IsValid() const override { return !std::isnan(Value); }
|
||||
virtual void Allocate(WorldGraphAllocatorBase* a) const override { a->Allocate(*this); }
|
||||
virtual void SetInput(int, WorldNodeBase*) override { assert(false); }
|
||||
};
|
||||
|
||||
// ─── Noise base ───────────────────────────────────────────────────────────────
|
||||
|
||||
struct WorldNode_NoiseBase : public WorldNodeBase
|
||||
{
|
||||
float Frequency{ 1.f };
|
||||
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
virtual NodeValue 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);
|
||||
return NodeValue(EvaluateNoise(params.Seed, params.X * Frequency, params.Y * Frequency));
|
||||
}
|
||||
virtual NodeValueType GetReturnType() const override { return NodeValueType::Float; }
|
||||
virtual std::vector<NodeValueType> GetInputTypes() const override { return {}; }
|
||||
virtual bool IsValid() const override { return Frequency != 0.f; }
|
||||
virtual void Allocate(WorldGraphAllocatorBase* a) const override { a->Allocate(*this); }
|
||||
virtual void SetInput(int, WorldNodeBase*) override { 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
|
||||
float EvaluateNoise(int seed, float x, float y) const override
|
||||
{
|
||||
const float SQRT3 = (float)1.7320508075688772935274463415059;
|
||||
const float F2 = 0.5f * (SQRT3 - 1);
|
||||
const float F2 = 0.5f * (1.7320508075688772935f - 1.f);
|
||||
float t = (x + y) * F2;
|
||||
x += t;
|
||||
y += t;
|
||||
|
||||
return fastnoiselitestatic::SingleSimplex<float>(seed, x, y);
|
||||
return fastnoiselitestatic::SingleSimplex<float>(seed, x + t, y + t);
|
||||
}
|
||||
};
|
||||
|
||||
struct WorldNode_OpenSimplex : public WorldNode_NoiseBase
|
||||
{
|
||||
virtual float EvaluateNoise(int seed, float x, float y) const override
|
||||
float EvaluateNoise(int seed, float x, float y) const override
|
||||
{
|
||||
const float SQRT3 = (float)1.7320508075688772935274463415059;
|
||||
const float F2 = 0.5f * (SQRT3 - 1);
|
||||
const float F2 = 0.5f * (1.7320508075688772935f - 1.f);
|
||||
float t = (x + y) * F2;
|
||||
x += t;
|
||||
y += t;
|
||||
|
||||
return fastnoiselitestatic::SingleOpenSimplex2S<float>(seed, x, y);
|
||||
return fastnoiselitestatic::SingleOpenSimplex2S<float>(seed, x + t, y + t);
|
||||
}
|
||||
};
|
||||
struct WorldNode_Perlin : public WorldNode_NoiseBase { float EvaluateNoise(int s,float x,float y)const override{return fastnoiselitestatic::SinglePerlin<float>(s,x,y);} };
|
||||
struct WorldNode_ValueCubic : public WorldNode_NoiseBase { float EvaluateNoise(int s,float x,float y)const override{return fastnoiselitestatic::SingleValueCubic<float>(s,x,y);} };
|
||||
struct WorldNode_Value : public WorldNode_NoiseBase { float EvaluateNoise(int s,float x,float y)const override{return fastnoiselitestatic::SingleValue<float>(s,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);
|
||||
}
|
||||
};
|
||||
// ─── Tile query nodes ─────────────────────────────────────────────────────────
|
||||
|
||||
struct WorldNode_IsTile : public WorldNodeBase
|
||||
{
|
||||
int8_t RelativeX{};
|
||||
int8_t RelativeY{};
|
||||
TILE_TYPE TileType{};
|
||||
TileType Type{};
|
||||
|
||||
virtual Variant Evaluate(const WorldNodeParameters& params) const override
|
||||
virtual NodeValue 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);
|
||||
return NodeValue(params.GetTile(params.X + RelativeX, params.Y + RelativeY).GetType() == Type);
|
||||
}
|
||||
virtual NodeValueType GetReturnType() const override { return NodeValueType::Bool; }
|
||||
virtual std::vector<NodeValueType> GetInputTypes() const override { return {}; }
|
||||
virtual bool IsValid() const override { return true; }
|
||||
virtual void Allocate(WorldGraphAllocatorBase* a) const override { a->Allocate(*this); }
|
||||
virtual void SetInput(int, WorldNodeBase*) override { assert(false); }
|
||||
};
|
||||
|
||||
struct WorldNode_TileDistance : public WorldNodeBase
|
||||
{
|
||||
int8_t Range{};
|
||||
TILE_TYPE TileType{};
|
||||
TileType Type{};
|
||||
|
||||
private:
|
||||
struct SmallVectorI2{
|
||||
int8_t X; int8_t Y;
|
||||
SmallVectorI2(int8_t x, int8_t y) : X{ x }, Y{ y } {};
|
||||
SmallVectorI2() = default;
|
||||
};
|
||||
struct SmallVec2I { int8_t X, Y; SmallVec2I(int8_t x,int8_t y):X{x},Y{y}{} SmallVec2I()=default; };
|
||||
|
||||
static constexpr int ArraySize{(WorldNodeParameters::MaxQueryOffset * 2 + 1) * (WorldNodeParameters::MaxQueryOffset * 2 + 1) - 1};
|
||||
static constexpr int ArraySize =
|
||||
(WorldNodeParameters::MaxQueryOffset * 2 + 1) *
|
||||
(WorldNodeParameters::MaxQueryOffset * 2 + 1) - 1;
|
||||
|
||||
static std::array<SmallVectorI2, ArraySize> CreateSortedOffsets()
|
||||
static std::array<SmallVec2I, 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;
|
||||
std::array<SmallVec2I, ArraySize> offsets{};
|
||||
int n{};
|
||||
for (int y = -WorldNodeParameters::MaxQueryOffset; y <= WorldNodeParameters::MaxQueryOffset; ++y)
|
||||
for (int x = -WorldNodeParameters::MaxQueryOffset; x <= WorldNodeParameters::MaxQueryOffset; ++x)
|
||||
if (x != 0 || y != 0)
|
||||
offsets[n++] = SmallVec2I{ static_cast<int8_t>(x), static_cast<int8_t>(y) };
|
||||
std::sort(offsets.begin(), offsets.end(), [](SmallVec2I l, SmallVec2I r) {
|
||||
return r.X*r.X + r.Y*r.Y > l.X*l.X + l.Y*l.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);
|
||||
int side = (WorldNodeParameters::MaxQueryOffset - i) * 2 + 1;
|
||||
offsets[i] = ArraySize - (side * side - 1);
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
inline static const std::array<SmallVec2I, ArraySize> SortedOffsets{ CreateSortedOffsets() };
|
||||
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
|
||||
virtual NodeValue Evaluate(const WorldNodeParameters& params) const override
|
||||
{
|
||||
if (!params.ChunkInfo.GetBounds().has_point(Vector2i{params.X, params.Y})) return 16'384.f;
|
||||
if (!params.ChunkInfo.GetBounds().HasPoint(params.X, params.Y))
|
||||
return NodeValue(16384.f);
|
||||
|
||||
int maxRangeSQ = 16'384;
|
||||
for (int i{RangeOffsets[Range]}; i < 48; ++i)
|
||||
int maxRangeSQ = 16384;
|
||||
for (int i = RangeOffsets[Range]; i < ArraySize; ++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;
|
||||
auto off = SortedOffsets[i];
|
||||
if (params.GetTile(params.X + off.X, params.Y + off.Y).GetType() == Type)
|
||||
maxRangeSQ = off.X * off.X + off.Y * off.Y;
|
||||
}
|
||||
return NodeValue(std::sqrt(static_cast<float>(maxRangeSQ)));
|
||||
}
|
||||
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);
|
||||
}
|
||||
virtual NodeValueType GetReturnType() const override { return NodeValueType::Float; }
|
||||
virtual std::vector<NodeValueType> GetInputTypes() const override { return {}; }
|
||||
virtual bool IsValid() const override { return Range >= 1 && Range <= 3; }
|
||||
virtual void Allocate(WorldGraphAllocatorBase* a) const override { a->Allocate(*this); }
|
||||
virtual void SetInput(int, WorldNodeBase*) override { 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 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "WorldGraphNode.h"
|
||||
#include "core/io/resource.h"
|
||||
#include "modules/factory/include/Util/Helpers.h"
|
||||
#include "flecs.h"
|
||||
#include "Types/Tile.h"
|
||||
#include <cstdint>
|
||||
|
||||
class WorldGraphVisualNodeBase : public Resource
|
||||
// ─── Node kind enum ────────────────────────────────────────────────────────────
|
||||
// Each value corresponds to one WorldNode_* runtime type.
|
||||
|
||||
enum class VisualNodeKind : uint32_t
|
||||
{
|
||||
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{};
|
||||
// Binary float→float
|
||||
Add, Subtract, Multiply, Divide, Modulo, Pow, Max, Min,
|
||||
// Unary float→float
|
||||
Negate, Abs, Ceil, Floor, Sin, Cos, Tan, Exp, Log, Square, Round, OneMinus,
|
||||
// Ternary float→float
|
||||
Lerp, Clamp,
|
||||
// Comparison float→bool
|
||||
Equal, Smaller, Greater, SmallerEqual, GreaterEqual,
|
||||
// Logic bool→bool
|
||||
And, Or,
|
||||
// Branch
|
||||
Branch,
|
||||
// Noise (leaf, float output)
|
||||
Simplex, OpenSimplex, Perlin, Value, ValueCubic,
|
||||
// Other leaf nodes
|
||||
Constant, IsTile, TileDistance,
|
||||
};
|
||||
|
||||
class WorldGraphVisualNode_Math : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_Math, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
// ─── Core components (every node entity has these) ────────────────────────────
|
||||
|
||||
public:
|
||||
WorldGraphVisualNode_Math();
|
||||
virtual ~WorldGraphVisualNode_Math() = default;
|
||||
struct VisualNodeType { VisualNodeKind kind; };
|
||||
struct VisualNodePos { float x, y; }; // canvas position in editor
|
||||
|
||||
public:
|
||||
TypedArray<String> GetNodeNames() const;
|
||||
void SetNode(String nodeName);
|
||||
String GetNode() const { return NodeID; }
|
||||
// Mark this node as the graph's output (execution root). Only one per graph.
|
||||
struct VisualNodeOutput {};
|
||||
|
||||
private:
|
||||
String NodeID{};
|
||||
// ─── Input connection relationships ───────────────────────────────────────────
|
||||
// To connect node B's input slot 0 to node A's output:
|
||||
// b.add<InputPin0>(a);
|
||||
// Flecs serializes entity relationship targets by name, so these round-trip
|
||||
// correctly through to_json / from_json.
|
||||
|
||||
};
|
||||
struct InputPin0 {}; // first input slot
|
||||
struct InputPin1 {}; // second input slot
|
||||
struct InputPin2 {}; // third input slot (Branch: bool, true, false)
|
||||
|
||||
class WorldGraphVisualNode_Constant : public WorldGraphVisualNodeBase
|
||||
{
|
||||
GDCLASS(WorldGraphVisualNode_Constant, WorldGraphVisualNodeBase);
|
||||
public:
|
||||
static void _bind_methods();
|
||||
// ─── Leaf node parameter components ──────────────────────────────────────────
|
||||
// Only nodes that carry configurable parameters have these.
|
||||
|
||||
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;
|
||||
};
|
||||
struct NodeParam_Constant { float value; };
|
||||
struct NodeParam_Noise { float frequency; };
|
||||
struct NodeParam_IsTile { int8_t relativeX; int8_t relativeY; TileType tileType; };
|
||||
struct NodeParam_TileDistance{ int8_t range; TileType tileType; };
|
||||
|
||||
@@ -752,8 +752,8 @@ namespace fastnoiselitestatic {
|
||||
float distance = 1e10f;
|
||||
constexpr float cellularJitter = 0.43701595f /** mCellularJitterModifier*/;
|
||||
|
||||
constexpr int xPrimed = (x - 1) * PrimeX;
|
||||
constexpr int yPrimedBase = (y - 1) * PrimeY;
|
||||
int xPrimed = (x - 1) * PrimeX;
|
||||
int yPrimedBase = (y - 1) * PrimeY;
|
||||
|
||||
for (int xi = x - 1; xi <= x + 1; xi++)
|
||||
{
|
||||
|
||||
186
src/Components/Support.cpp
Normal file
186
src/Components/Support.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include "Components/Support.h"
|
||||
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct SupportNode
|
||||
{
|
||||
flecs::entity Entity;
|
||||
Vector2 Pos;
|
||||
};
|
||||
|
||||
bool InSpan(tcb::span<const Vector2> span, Vector2 pos)
|
||||
{
|
||||
return std::any_of(span.begin(), span.end(), [&](const Vector2& p) {
|
||||
return p.X == pos.X && p.Y == pos.Y;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
flecs::entity GetSupport(flecs::world& world, Vector2 pos)
|
||||
{
|
||||
flecs::entity result{};
|
||||
world.each([&](flecs::entity e, const TilePosition& tp, const Support&) {
|
||||
if (tp.Position.X == pos.X && tp.Position.Y == pos.Y)
|
||||
result = e;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void RecalculateSupport(flecs::world& world,
|
||||
tcb::span<const Vector2> skip,
|
||||
tcb::span<const Vector2> unground)
|
||||
{
|
||||
// Collect all support nodes first (avoid nested world.each() re-entrancy)
|
||||
std::vector<SupportNode> nodes;
|
||||
world.each([&](flecs::entity e, const TilePosition& tp, const Support&) {
|
||||
if (!InSpan(skip, tp.Position))
|
||||
nodes.push_back({ e, tp.Position });
|
||||
});
|
||||
|
||||
// Reset all SupportsAvailable to 0
|
||||
for (auto& node : nodes)
|
||||
node.Entity.ensure<Support>().SupportsAvailable = 0;
|
||||
|
||||
// Seed queue with grounded supports
|
||||
std::queue<SupportNode> queue;
|
||||
for (auto& node : nodes)
|
||||
{
|
||||
if (node.Entity.has<GroundedSupport>() && !InSpan(unground, node.Pos))
|
||||
{
|
||||
uint8_t maxSupport = node.Entity.get<Support>().MaxSupport;
|
||||
node.Entity.ensure<Support>().SupportsAvailable = maxSupport;
|
||||
queue.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
// BFS: propagate support outward from grounded anchors
|
||||
const Vector2 offsets[] = { {0, 1}, {0, -1}, {1, 0}, {-1, 0} };
|
||||
while (!queue.empty())
|
||||
{
|
||||
SupportNode current = queue.front();
|
||||
queue.pop();
|
||||
|
||||
uint8_t currentAvailable = current.Entity.get<Support>().SupportsAvailable;
|
||||
|
||||
for (const auto& offset : offsets)
|
||||
{
|
||||
uint8_t cost = (offset.X != 0) ? 2 : 1;
|
||||
if (currentAvailable < cost) continue;
|
||||
|
||||
Vector2 neighborPos = current.Pos + offset;
|
||||
|
||||
auto it = std::find_if(nodes.begin(), nodes.end(), [&](const SupportNode& n) {
|
||||
return n.Pos.X == neighborPos.X && n.Pos.Y == neighborPos.Y;
|
||||
});
|
||||
if (it == nodes.end()) continue;
|
||||
|
||||
uint8_t neighborMax = it->Entity.get<Support>().MaxSupport;
|
||||
uint8_t candidate = std::min(static_cast<uint8_t>(currentAvailable - cost), neighborMax);
|
||||
uint8_t neighborCurrent = it->Entity.get<Support>().SupportsAvailable;
|
||||
|
||||
if (candidate > neighborCurrent)
|
||||
{
|
||||
it->Entity.ensure<Support>().SupportsAvailable = candidate;
|
||||
queue.push(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CanRemove(flecs::world& world, tcb::span<const Vector2> positions)
|
||||
{
|
||||
RecalculateSupport(world, positions);
|
||||
|
||||
bool valid = true;
|
||||
|
||||
// Collect remaining positions with active support; flag any that lost all support
|
||||
std::vector<Vector2> supportedPositions;
|
||||
world.each([&](const TilePosition& tp, const Support& s) {
|
||||
if (InSpan(positions, tp.Position)) return;
|
||||
if (s.SupportsAvailable == 0)
|
||||
valid = false;
|
||||
else
|
||||
supportedPositions.push_back(tp.Position);
|
||||
});
|
||||
|
||||
// Check every RequiresSupport entity has an active support one tile below it
|
||||
world.each([&](flecs::entity e, const TilePosition& tp) {
|
||||
if (e.has<RequiresSupport>())
|
||||
{
|
||||
Vector2 supportPos = { tp.Position.X, tp.Position.Y - 1 };
|
||||
tcb::span<const Vector2> suppSpan(supportedPositions.data(), supportedPositions.size());
|
||||
if (!InSpan(suppSpan, supportPos))
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
|
||||
RecalculateSupport(world);
|
||||
return valid;
|
||||
}
|
||||
|
||||
void Flecs_Support(flecs::world& world)
|
||||
{
|
||||
world.component<Support>()
|
||||
.member<uint8_t>("MaxSupport")
|
||||
.member<uint8_t>("SupportsAvailable");
|
||||
|
||||
world.component<GroundedSupport>();
|
||||
world.component<RequiresSupport>();
|
||||
|
||||
// OnSet fires after the value is written (unlike OnAdd which fires during archetype move,
|
||||
// before the actual struct values are copied in — MaxSupport would still be 0 at that point).
|
||||
world.observer<Support>("On Support Set")
|
||||
.event(flecs::OnSet)
|
||||
.each([](flecs::entity e, Support&) {
|
||||
auto w = e.world();
|
||||
RecalculateSupport(w);
|
||||
});
|
||||
|
||||
world.observer<Support>("On Support Remove")
|
||||
.event(flecs::OnRemove)
|
||||
.each([](flecs::entity e, Support&) {
|
||||
auto w = e.world();
|
||||
if (e.has<TilePosition>())
|
||||
{
|
||||
Vector2 pos = e.get<TilePosition>().Position;
|
||||
RecalculateSupport(w, tcb::span<const Vector2>(&pos, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
RecalculateSupport(w);
|
||||
}
|
||||
});
|
||||
|
||||
// GroundedSupport is a tag (no data) — use .with<>() filter so the lambda
|
||||
// takes only flecs::entity, avoiding the empty-type column issue.
|
||||
world.observer("On GroundedSupport Add")
|
||||
.with<GroundedSupport>()
|
||||
.event(flecs::OnAdd)
|
||||
.each([](flecs::entity e) {
|
||||
auto w = e.world();
|
||||
RecalculateSupport(w);
|
||||
});
|
||||
|
||||
// OnRemove fires before actual removal, so entity still has GroundedSupport.
|
||||
// Pass position to unground so the entity is not treated as a grounded seed.
|
||||
world.observer("On GroundedSupport Remove")
|
||||
.with<GroundedSupport>()
|
||||
.event(flecs::OnRemove)
|
||||
.each([](flecs::entity e) {
|
||||
auto w = e.world();
|
||||
if (e.has<TilePosition>())
|
||||
{
|
||||
Vector2 pos = e.get<TilePosition>().Position;
|
||||
RecalculateSupport(w, {}, tcb::span<const Vector2>(&pos, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
RecalculateSupport(w);
|
||||
}
|
||||
});
|
||||
}
|
||||
208
src/Components/WorldGraph.cpp
Normal file
208
src/Components/WorldGraph.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include "Types/WorldGraph/WorldGraph.h"
|
||||
#include "Types/WorldGraph/WorldGraphAllocator.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cassert>
|
||||
|
||||
// ─── Helper: allocate the right runtime node for an entity ────────────────────
|
||||
|
||||
static WorldNodeBase* AllocateNode(WorldGraphAllocatorBase& alloc, flecs::entity e)
|
||||
{
|
||||
const auto* t = e.try_get<VisualNodeType>();
|
||||
if (!t) return nullptr;
|
||||
|
||||
switch (t->kind)
|
||||
{
|
||||
// ── Binary float→float ──────────────────────────────────────────────
|
||||
case VisualNodeKind::Add: return alloc.Allocate(WorldNode_Add{});
|
||||
case VisualNodeKind::Subtract: return alloc.Allocate(WorldNode_Subtract{});
|
||||
case VisualNodeKind::Multiply: return alloc.Allocate(WorldNode_Multiply{});
|
||||
case VisualNodeKind::Divide: return alloc.Allocate(WorldNode_Divide{});
|
||||
case VisualNodeKind::Modulo: return alloc.Allocate(WorldNode_Modulo{});
|
||||
case VisualNodeKind::Pow: return alloc.Allocate(WorldNode_Pow{});
|
||||
case VisualNodeKind::Max: return alloc.Allocate(WorldNode_Max{});
|
||||
case VisualNodeKind::Min: return alloc.Allocate(WorldNode_Min{});
|
||||
|
||||
// ── Unary float→float ───────────────────────────────────────────────
|
||||
case VisualNodeKind::Negate: return alloc.Allocate(WorldNode_Negate{});
|
||||
case VisualNodeKind::Abs: return alloc.Allocate(WorldNode_Abs{});
|
||||
case VisualNodeKind::Ceil: return alloc.Allocate(WorldNode_Ceil{});
|
||||
case VisualNodeKind::Floor: return alloc.Allocate(WorldNode_Floor{});
|
||||
case VisualNodeKind::Sin: return alloc.Allocate(WorldNode_Sin{});
|
||||
case VisualNodeKind::Cos: return alloc.Allocate(WorldNode_Cos{});
|
||||
case VisualNodeKind::Tan: return alloc.Allocate(WorldNode_Tan{});
|
||||
case VisualNodeKind::Exp: return alloc.Allocate(WorldNode_Exp{});
|
||||
case VisualNodeKind::Log: return alloc.Allocate(WorldNode_Log{});
|
||||
case VisualNodeKind::Square: return alloc.Allocate(WorldNode_Square{});
|
||||
case VisualNodeKind::Round: return alloc.Allocate(WorldNode_Round{});
|
||||
case VisualNodeKind::OneMinus: return alloc.Allocate(WorldNode_OneMinus{});
|
||||
|
||||
// ── Ternary float→float ─────────────────────────────────────────────
|
||||
case VisualNodeKind::Lerp: return alloc.Allocate(WorldNode_Lerp{});
|
||||
case VisualNodeKind::Clamp: return alloc.Allocate(WorldNode_Clamp{});
|
||||
|
||||
// ── Comparison float→bool ───────────────────────────────────────────
|
||||
case VisualNodeKind::Equal: return alloc.Allocate(WorldNode_Equal{});
|
||||
case VisualNodeKind::Smaller: return alloc.Allocate(WorldNode_Smaller{});
|
||||
case VisualNodeKind::Greater: return alloc.Allocate(WorldNode_Greater{});
|
||||
case VisualNodeKind::SmallerEqual: return alloc.Allocate(WorldNode_SmallerEqual{});
|
||||
case VisualNodeKind::GreaterEqual: return alloc.Allocate(WorldNode_GreaterEqual{});
|
||||
|
||||
// ── Logic ───────────────────────────────────────────────────────────
|
||||
case VisualNodeKind::And: return alloc.Allocate(WorldNode_And{});
|
||||
case VisualNodeKind::Or: return alloc.Allocate(WorldNode_Or{});
|
||||
|
||||
// ── Branch ──────────────────────────────────────────────────────────
|
||||
case VisualNodeKind::Branch: return alloc.Allocate(WorldNode_Branch{});
|
||||
|
||||
// ── Noise ───────────────────────────────────────────────────────────
|
||||
case VisualNodeKind::Simplex: {
|
||||
WorldNode_Simplex n;
|
||||
if (const auto* p = e.try_get<NodeParam_Noise>()) n.Frequency = p->frequency;
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
case VisualNodeKind::OpenSimplex: {
|
||||
WorldNode_OpenSimplex n;
|
||||
if (const auto* p = e.try_get<NodeParam_Noise>()) n.Frequency = p->frequency;
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
case VisualNodeKind::Perlin: {
|
||||
WorldNode_Perlin n;
|
||||
if (const auto* p = e.try_get<NodeParam_Noise>()) n.Frequency = p->frequency;
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
case VisualNodeKind::Value: {
|
||||
WorldNode_Value n;
|
||||
if (const auto* p = e.try_get<NodeParam_Noise>()) n.Frequency = p->frequency;
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
case VisualNodeKind::ValueCubic: {
|
||||
WorldNode_ValueCubic n;
|
||||
if (const auto* p = e.try_get<NodeParam_Noise>()) n.Frequency = p->frequency;
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
|
||||
// ── Constant ────────────────────────────────────────────────────────
|
||||
case VisualNodeKind::Constant: {
|
||||
WorldNode_Constant n;
|
||||
if (const auto* p = e.try_get<NodeParam_Constant>()) n.Value = p->value;
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
|
||||
// ── IsTile ──────────────────────────────────────────────────────────
|
||||
case VisualNodeKind::IsTile: {
|
||||
WorldNode_IsTile n;
|
||||
if (const auto* p = e.try_get<NodeParam_IsTile>())
|
||||
{
|
||||
n.RelativeX = p->relativeX;
|
||||
n.RelativeY = p->relativeY;
|
||||
n.Type = p->tileType;
|
||||
}
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
|
||||
// ── TileDistance ────────────────────────────────────────────────────
|
||||
case VisualNodeKind::TileDistance: {
|
||||
WorldNode_TileDistance n;
|
||||
if (const auto* p = e.try_get<NodeParam_TileDistance>())
|
||||
{
|
||||
n.Range = p->range;
|
||||
n.Type = p->tileType;
|
||||
}
|
||||
return alloc.Allocate(n);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ─── Wire input connections for one node ──────────────────────────────────────
|
||||
|
||||
static void WireInputs(
|
||||
flecs::entity e,
|
||||
WorldNodeBase* runtimeNode,
|
||||
const std::unordered_map<uint64_t, WorldNodeBase*>& nodeMap)
|
||||
{
|
||||
auto wire = [&](int slot, flecs::entity src)
|
||||
{
|
||||
if (!src) return;
|
||||
auto it = nodeMap.find(src.id());
|
||||
if (it == nodeMap.end()) return;
|
||||
runtimeNode->SetInput(slot, it->second);
|
||||
};
|
||||
|
||||
wire(0, e.target<InputPin0>());
|
||||
wire(1, e.target<InputPin1>());
|
||||
wire(2, e.target<InputPin2>());
|
||||
}
|
||||
|
||||
// ─── WorldGraph::Compile ──────────────────────────────────────────────────────
|
||||
|
||||
WorldGraph WorldGraph::Compile(flecs::world& world, flecs::entity graphRoot)
|
||||
{
|
||||
// 1. Collect all child node entities
|
||||
std::vector<flecs::entity> nodeEntities;
|
||||
world.query_builder<VisualNodeType>()
|
||||
.with(flecs::ChildOf, graphRoot)
|
||||
.build()
|
||||
.each([&](flecs::entity e, VisualNodeType&)
|
||||
{
|
||||
nodeEntities.push_back(e);
|
||||
});
|
||||
|
||||
if (nodeEntities.empty())
|
||||
return {};
|
||||
|
||||
// 2. Measure total memory needed
|
||||
WorldGraphSizeMeasurer measurer;
|
||||
for (auto e : nodeEntities)
|
||||
AllocateNode(measurer, e);
|
||||
|
||||
// 3. Allocate buffer and fill it
|
||||
WorldGraphAllocator alloc(measurer.TotalSize);
|
||||
|
||||
std::unordered_map<uint64_t, WorldNodeBase*> nodeMap;
|
||||
nodeMap.reserve(nodeEntities.size());
|
||||
|
||||
for (auto e : nodeEntities)
|
||||
{
|
||||
WorldNodeBase* runtimeNode = AllocateNode(alloc, e);
|
||||
if (runtimeNode)
|
||||
nodeMap[e.id()] = runtimeNode;
|
||||
}
|
||||
|
||||
// 4. Wire connections
|
||||
for (auto e : nodeEntities)
|
||||
{
|
||||
auto it = nodeMap.find(e.id());
|
||||
if (it != nodeMap.end())
|
||||
WireInputs(e, it->second, nodeMap);
|
||||
}
|
||||
|
||||
// 5. Find the output node
|
||||
WorldNodeBase* rootNode = nullptr;
|
||||
for (auto e : nodeEntities)
|
||||
{
|
||||
if (e.has<VisualNodeOutput>())
|
||||
{
|
||||
auto it = nodeMap.find(e.id());
|
||||
if (it != nodeMap.end())
|
||||
rootNode = it->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Build and return the compiled graph
|
||||
WorldGraph graph;
|
||||
graph.MemorySize = alloc.CurrentOffset;
|
||||
graph.CompiledData = std::move(alloc.Data);
|
||||
graph.RootNode = rootNode;
|
||||
return graph;
|
||||
}
|
||||
|
||||
// ─── WorldGraph::Execute ─────────────────────────────────────────────────────
|
||||
|
||||
NodeValue WorldGraph::Execute(const WorldNodeParameters& params) const
|
||||
{
|
||||
assert(IsValid());
|
||||
return RootNode->Evaluate(params);
|
||||
}
|
||||
0
src/Components/components.cpp
Normal file
0
src/Components/components.cpp
Normal file
@@ -5,6 +5,9 @@
|
||||
#include "Components/Resource.hpp"
|
||||
#include "Components/Inventory.hpp"
|
||||
#include "Components/Tick.hpp"
|
||||
#include "Components/Chute.hpp"
|
||||
#include "Components/Support.h"
|
||||
#include "Components/WorldGraph.hpp"
|
||||
|
||||
WorldInstance::WorldInstance(const WorldConfig& worldConfig)
|
||||
{
|
||||
@@ -22,7 +25,11 @@ void WorldInstance::RegisterTypes(flecs::world &world)
|
||||
Flecs_Item(world);
|
||||
Flecs_Configs(world);
|
||||
Flecs_Tick(world);
|
||||
Flecs_Inventory(world);
|
||||
Flecs_Resource(world);
|
||||
Flecs_Chute(world);
|
||||
Flecs_Support(world);
|
||||
Flecs_WorldGraph(world);
|
||||
}
|
||||
|
||||
void WorldInstance::ProcessFrame()
|
||||
|
||||
162
tests/Components/test_Chute.cpp
Normal file
162
tests/Components/test_Chute.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include <doctest/doctest.h>
|
||||
#include "Components/Configs/WorldConfig.hpp"
|
||||
#include "Core/WorldInstance.h"
|
||||
#include "Components/Chute.hpp"
|
||||
|
||||
TEST_SUITE("Chute") {
|
||||
TEST_CASE("chute transports item from source to destination") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
// source inventory with items, destination empty
|
||||
auto source = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(source, config, 100);
|
||||
source.ensure<Inventory>().AddItems(stoneID, 3);
|
||||
|
||||
auto dest = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(dest, config, 100);
|
||||
|
||||
// vertical drop: (0,10) -> (0,0), should be fast
|
||||
std::vector<Vector2> path = { {0, 10}, {0, 0} };
|
||||
auto chuteEntity = world.GetEcsWorld().entity();
|
||||
Chute_Helper(chuteEntity, path,
|
||||
source.get<Inventory>(),
|
||||
dest.get<Inventory>());
|
||||
|
||||
// get the transit time
|
||||
auto chute = chuteEntity.get<Chute>();
|
||||
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
|
||||
CHECK(transitTicks > 0);
|
||||
|
||||
// tick once to pull items from source into chute
|
||||
world.ProcessFrame();
|
||||
|
||||
auto srcInv = source.get<Inventory>();
|
||||
CHECK(srcInv.GetItemsAmount(stoneID) == 0);
|
||||
|
||||
// tick until items arrive
|
||||
for (uint16_t i = 1; i < transitTicks; ++i)
|
||||
world.ProcessFrame();
|
||||
|
||||
auto destInv = dest.get<Inventory>();
|
||||
CHECK(destInv.GetItemsAmount(stoneID) == 0);
|
||||
|
||||
world.ProcessFrame();
|
||||
|
||||
destInv = dest.get<Inventory>();
|
||||
CHECK(destInv.GetItemsAmount(stoneID) == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("chute respects transit time for longer paths") {
|
||||
WorldConfig config{};
|
||||
uint16_t ironID = config.RegisterItem("Iron");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto source = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(source, config, 100);
|
||||
source.ensure<Inventory>().AddItems(ironID, 1);
|
||||
|
||||
auto dest = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(dest, config, 100);
|
||||
|
||||
// multi-link path with gradual descent
|
||||
std::vector<Vector2> path = { {0, 10}, {1, 9}, {2, 8}, {3, 7}, {4, 6}, {5, 5} };
|
||||
auto chuteEntity = world.GetEcsWorld().entity();
|
||||
Chute_Helper(chuteEntity, path,
|
||||
source.get<Inventory>(),
|
||||
dest.get<Inventory>());
|
||||
|
||||
auto chute = chuteEntity.get<Chute>();
|
||||
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
|
||||
CHECK(transitTicks > 1);
|
||||
|
||||
// pull items into chute
|
||||
world.ProcessFrame();
|
||||
|
||||
// tick one less than transit time — item should not have arrived
|
||||
for (uint16_t i = 1; i < transitTicks; ++i)
|
||||
world.ProcessFrame();
|
||||
|
||||
auto destInv = dest.get<Inventory>();
|
||||
CHECK(destInv.GetItemsAmount(ironID) == 0);
|
||||
|
||||
// one more tick — item arrives
|
||||
world.ProcessFrame();
|
||||
|
||||
destInv = dest.get<Inventory>();
|
||||
CHECK(destInv.GetItemsAmount(ironID) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("chute overflows to world inventory when destination is full") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto source = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(source, config, 100);
|
||||
source.ensure<Inventory>().AddItems(stoneID, 3);
|
||||
|
||||
// destination can only hold 1
|
||||
auto dest = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(dest, config, 1);
|
||||
|
||||
std::vector<Vector2> path = { {0, 10}, {0, 0} };
|
||||
auto chuteEntity = world.GetEcsWorld().entity();
|
||||
Chute_Helper(chuteEntity, path,
|
||||
source.get<Inventory>(),
|
||||
dest.get<Inventory>());
|
||||
|
||||
auto chute = chuteEntity.get<Chute>();
|
||||
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
|
||||
|
||||
// tick enough for all items to arrive
|
||||
for (uint16_t i = 0; i <= transitTicks; ++i)
|
||||
world.ProcessFrame();
|
||||
|
||||
auto destInv = dest.get<Inventory>();
|
||||
CHECK(destInv.GetItemsAmount(stoneID) == 1);
|
||||
|
||||
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(worldInv.GetItemsAmount(stoneID) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("horizontal chute uses minimum speed") {
|
||||
WorldConfig config{};
|
||||
uint16_t woodID = config.RegisterItem("Wood");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto source = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(source, config, 100);
|
||||
source.ensure<Inventory>().AddItems(woodID, 1);
|
||||
|
||||
auto dest = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(dest, config, 100);
|
||||
|
||||
// flat path: dy=0 throughout, should use MinSpeed
|
||||
ChuteConfig chuteConfig{ .Gravity = 1.0f, .MinSpeed = 0.5f };
|
||||
std::vector<Vector2> path = { {0, 0}, {5, 0} };
|
||||
auto chuteEntity = world.GetEcsWorld().entity();
|
||||
Chute_Helper(chuteEntity, path,
|
||||
source.get<Inventory>(),
|
||||
dest.get<Inventory>(),
|
||||
chuteConfig);
|
||||
|
||||
auto chute = chuteEntity.get<Chute>();
|
||||
uint16_t transitTicks = chute.Data.GetMetaData()->TicksToReachEnd;
|
||||
|
||||
// distance=5, speed=0.5 -> 10 ticks
|
||||
CHECK(transitTicks == 10);
|
||||
|
||||
// tick enough for item to arrive
|
||||
for (uint16_t i = 0; i <= transitTicks; ++i)
|
||||
world.ProcessFrame();
|
||||
|
||||
auto destInv = dest.get<Inventory>();
|
||||
CHECK(destInv.GetItemsAmount(woodID) == 1);
|
||||
}
|
||||
}
|
||||
246
tests/Components/test_Resource.cpp
Normal file
246
tests/Components/test_Resource.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#include <doctest/doctest.h>
|
||||
#include "Components/Configs/WorldConfig.hpp"
|
||||
#include "Core/WorldInstance.h"
|
||||
#include "Components/Resource.hpp"
|
||||
#include "Components/Inventory.hpp"
|
||||
|
||||
TEST_SUITE("Resource") {
|
||||
TEST_CASE("basic resource gathering produces item after one tick") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
// Create a resource entity with gatherTicks = 1
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(entity, stoneID, 1);
|
||||
|
||||
world.ProcessFrame();
|
||||
|
||||
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(inv.GetItemsAmount(stoneID) >= 1);
|
||||
}
|
||||
|
||||
TEST_CASE("resource with 20 tick gather time produces item after 20 ticks") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(entity, stoneID, 20);
|
||||
|
||||
for (int i = 0; i < 19; ++i)
|
||||
world.ProcessFrame();
|
||||
|
||||
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(inv.GetItemsAmount(stoneID) == 0);
|
||||
|
||||
world.ProcessFrame();
|
||||
|
||||
CHECK(inv.GetItemsAmount(stoneID) >= 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("Resource - Health & Renewing") {
|
||||
TEST_CASE("tree resource loses health each gather cycle") {
|
||||
WorldConfig config{};
|
||||
uint16_t woodID = config.RegisterItem("Wood");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Tree_Helper(entity, woodID, 1, 3, 5);
|
||||
|
||||
world.ProcessFrame();
|
||||
|
||||
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(inv.GetItemsAmount(woodID) == 1);
|
||||
|
||||
auto health = entity.get<ResourceHealth>();
|
||||
CHECK(health.Health == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("tree enters renewing state when health reaches 0") {
|
||||
WorldConfig config{};
|
||||
uint16_t woodID = config.RegisterItem("Wood");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Tree_Helper(entity, woodID, 1, 2, 5);
|
||||
|
||||
// 2 gather cycles to deplete health
|
||||
world.ProcessFrame();
|
||||
world.ProcessFrame();
|
||||
|
||||
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(inv.GetItemsAmount(woodID) == 2);
|
||||
CHECK(entity.has<Renewing>());
|
||||
|
||||
// one more tick should not produce items while renewing
|
||||
world.ProcessFrame();
|
||||
CHECK(inv.GetItemsAmount(woodID) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("tree restores health after renewal period") {
|
||||
WorldConfig config{};
|
||||
uint16_t woodID = config.RegisterItem("Wood");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Tree_Helper(entity, woodID, 1, 1, 5);
|
||||
|
||||
// 1 gather cycle depletes health
|
||||
world.ProcessFrame();
|
||||
CHECK(entity.has<Renewing>());
|
||||
|
||||
// 5 ticks for renewal
|
||||
for (int i = 0; i < 5; ++i)
|
||||
world.ProcessFrame();
|
||||
|
||||
CHECK_FALSE(entity.has<Renewing>());
|
||||
CHECK(entity.has<FullyGrown>());
|
||||
|
||||
auto health = entity.get<ResourceHealth>();
|
||||
CHECK(health.Health == health.MaxHealth);
|
||||
}
|
||||
|
||||
TEST_CASE("tree resumes gathering after renewal") {
|
||||
WorldConfig config{};
|
||||
uint16_t woodID = config.RegisterItem("Wood");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Tree_Helper(entity, woodID, 1, 1, 5);
|
||||
|
||||
// deplete
|
||||
world.ProcessFrame();
|
||||
auto& inv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(inv.GetItemsAmount(woodID) == 1);
|
||||
|
||||
// renew (5 ticks)
|
||||
for (int i = 0; i < 5; ++i)
|
||||
world.ProcessFrame();
|
||||
|
||||
CHECK_FALSE(entity.has<Renewing>());
|
||||
|
||||
// should gather again
|
||||
world.ProcessFrame();
|
||||
CHECK(inv.GetItemsAmount(woodID) == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("Inventory Entity") {
|
||||
TEST_CASE("resource harvests into local inventory when present") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(entity, stoneID, 1);
|
||||
Inventory_Helper(entity, config, 10);
|
||||
|
||||
world.ProcessFrame();
|
||||
|
||||
auto localInv = entity.get<Inventory>();
|
||||
CHECK(localInv.GetItemsAmount(stoneID) == 1);
|
||||
|
||||
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(worldInv.GetItemsAmount(stoneID) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("overflow goes to world inventory when local is full") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto entity = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(entity, stoneID, 1);
|
||||
Inventory_Helper(entity, config, 2);
|
||||
|
||||
// fill local inventory (max 2)
|
||||
world.ProcessFrame();
|
||||
world.ProcessFrame();
|
||||
|
||||
auto localInv = entity.get<Inventory>();
|
||||
CHECK(localInv.GetItemsAmount(stoneID) == 2);
|
||||
|
||||
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(worldInv.GetItemsAmount(stoneID) == 0);
|
||||
|
||||
// next item should overflow to world inventory
|
||||
world.ProcessFrame();
|
||||
|
||||
localInv = entity.get<Inventory>();
|
||||
CHECK(localInv.GetItemsAmount(stoneID) == 2);
|
||||
CHECK(worldInv.GetItemsAmount(stoneID) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("inventory tracks multiple item types independently") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
uint16_t ironID = config.RegisterItem("Iron");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
auto stoneEntity = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(stoneEntity, stoneID, 1);
|
||||
Inventory_Helper(stoneEntity, config, 5);
|
||||
|
||||
auto ironEntity = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(ironEntity, ironID, 1);
|
||||
Inventory_Helper(ironEntity, config, 5);
|
||||
|
||||
world.ProcessFrame();
|
||||
|
||||
auto stoneInv = stoneEntity.get<Inventory>();
|
||||
CHECK(stoneInv.GetItemsAmount(stoneID) == 1);
|
||||
CHECK(stoneInv.GetItemsAmount(ironID) == 0);
|
||||
|
||||
auto ironInv = ironEntity.get<Inventory>();
|
||||
CHECK(ironInv.GetItemsAmount(ironID) == 1);
|
||||
CHECK(ironInv.GetItemsAmount(stoneID) == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("two producers share a separate inventory entity") {
|
||||
WorldConfig config{};
|
||||
uint16_t stoneID = config.RegisterItem("Stone");
|
||||
uint16_t ironID = config.RegisterItem("Iron");
|
||||
|
||||
WorldInstance world{ config };
|
||||
|
||||
// inventory entity (chest/barrel)
|
||||
auto chest = world.GetEcsWorld().entity();
|
||||
Inventory_Helper(chest, config, 10);
|
||||
|
||||
// copy the shared inventory to both producers
|
||||
auto chestInv = chest.get<Inventory>();
|
||||
|
||||
auto stoneProducer = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(stoneProducer, stoneID, 1);
|
||||
stoneProducer.set<Inventory>(chestInv);
|
||||
|
||||
auto ironProducer = world.GetEcsWorld().entity();
|
||||
Resource_Ore_Helper(ironProducer, ironID, 1);
|
||||
ironProducer.set<Inventory>(chestInv);
|
||||
|
||||
world.ProcessFrame();
|
||||
world.ProcessFrame();
|
||||
world.ProcessFrame();
|
||||
|
||||
// all items should be in the shared inventory
|
||||
auto resultInv = chest.get<Inventory>();
|
||||
CHECK(resultInv.GetItemsAmount(stoneID) == 3);
|
||||
CHECK(resultInv.GetItemsAmount(ironID) == 3);
|
||||
|
||||
// world inventory should be empty
|
||||
auto& worldInv = world.GetEcsWorld().ensure<WorldInventory>();
|
||||
CHECK(worldInv.GetItemsAmount(stoneID) == 0);
|
||||
CHECK(worldInv.GetItemsAmount(ironID) == 0);
|
||||
}
|
||||
}
|
||||
122
tests/Components/test_Support.cpp
Normal file
122
tests/Components/test_Support.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include <doctest/doctest.h>
|
||||
#include "Components/Configs/WorldConfig.hpp"
|
||||
#include "Core/WorldInstance.h"
|
||||
#include "Components/Support.h"
|
||||
|
||||
TEST_SUITE("Support") {
|
||||
TEST_CASE("grounded support has SupportsAvailable equal to MaxSupport") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
auto e = world.GetEcsWorld().entity();
|
||||
Support_Helper(e, { 0, 0 }, 5, true);
|
||||
|
||||
auto s = e.get<Support>();
|
||||
CHECK(s.SupportsAvailable == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("vertical neighbor costs 1 point") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
auto ground = world.GetEcsWorld().entity();
|
||||
Support_Helper(ground, { 0, 0 }, 5, true);
|
||||
|
||||
auto above = world.GetEcsWorld().entity();
|
||||
Support_Helper(above, { 0, 1 }, 5);
|
||||
|
||||
auto s = above.get<Support>();
|
||||
CHECK(s.SupportsAvailable == 4);
|
||||
}
|
||||
|
||||
TEST_CASE("horizontal neighbor costs 2 points") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
auto ground = world.GetEcsWorld().entity();
|
||||
Support_Helper(ground, { 0, 0 }, 5, true);
|
||||
|
||||
auto beside = world.GetEcsWorld().entity();
|
||||
Support_Helper(beside, { 1, 0 }, 5);
|
||||
|
||||
auto s = beside.get<Support>();
|
||||
CHECK(s.SupportsAvailable == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("floating support has SupportsAvailable zero") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
auto e = world.GetEcsWorld().entity();
|
||||
Support_Helper(e, { 5, 5 }, 5);
|
||||
|
||||
auto s = e.get<Support>();
|
||||
CHECK(s.SupportsAvailable == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("CanRemove returns false when removal disconnects a support") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
// chain: ground(0,0) -> (0,1) -> (0,2)
|
||||
auto ground = world.GetEcsWorld().entity();
|
||||
Support_Helper(ground, { 0, 0 }, 5, true);
|
||||
|
||||
auto mid = world.GetEcsWorld().entity();
|
||||
Support_Helper(mid, { 0, 1 }, 5);
|
||||
|
||||
auto top = world.GetEcsWorld().entity();
|
||||
Support_Helper(top, { 0, 2 }, 5);
|
||||
|
||||
// removing (0,1) disconnects (0,2)
|
||||
std::vector<Vector2> toRemove = { { 0, 1 } };
|
||||
CHECK_FALSE(CanRemove(world.GetEcsWorld(), toRemove));
|
||||
}
|
||||
|
||||
TEST_CASE("CanRemove returns true when removing a leaf node") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
auto ground = world.GetEcsWorld().entity();
|
||||
Support_Helper(ground, { 0, 0 }, 5, true);
|
||||
|
||||
auto leaf = world.GetEcsWorld().entity();
|
||||
Support_Helper(leaf, { 0, 1 }, 5);
|
||||
|
||||
// removing the leaf leaves only the grounded anchor — valid
|
||||
std::vector<Vector2> toRemove = { { 0, 1 } };
|
||||
CHECK(CanRemove(world.GetEcsWorld(), toRemove));
|
||||
}
|
||||
|
||||
TEST_CASE("CanRemove returns false when removal would unsupport a RequiresSupport entity") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
// grounded support at (0,0); machine one tile above at (0,1)
|
||||
auto ground = world.GetEcsWorld().entity();
|
||||
Support_Helper(ground, { 0, 0 }, 5, true);
|
||||
|
||||
auto machine = world.GetEcsWorld().entity();
|
||||
machine.set<TilePosition>({ { 0, 1 } });
|
||||
machine.add<RequiresSupport>();
|
||||
|
||||
// removing the only support at (0,0) leaves the machine at (0,1) unsupported
|
||||
std::vector<Vector2> toRemove = { { 0, 0 } };
|
||||
CHECK_FALSE(CanRemove(world.GetEcsWorld(), toRemove));
|
||||
}
|
||||
|
||||
TEST_CASE("CanRemove returns true when RequiresSupport entity still has support") {
|
||||
WorldInstance world{ WorldConfig{} };
|
||||
|
||||
// chain: ground(0,0) -> leaf(0,1); machine one tile above leaf at (0,2)
|
||||
auto ground = world.GetEcsWorld().entity();
|
||||
Support_Helper(ground, { 0, 0 }, 5, true);
|
||||
|
||||
auto leaf = world.GetEcsWorld().entity();
|
||||
Support_Helper(leaf, { 0, 1 }, 5);
|
||||
|
||||
auto machine = world.GetEcsWorld().entity();
|
||||
machine.set<TilePosition>({ { 0, 2 } });
|
||||
machine.add<RequiresSupport>();
|
||||
|
||||
// sibling at (1,0) is a horizontal leaf; removing it doesn't affect (0,1)
|
||||
auto sibling = world.GetEcsWorld().entity();
|
||||
Support_Helper(sibling, { 1, 0 }, 5);
|
||||
|
||||
std::vector<Vector2> toRemove = { { 1, 0 } };
|
||||
CHECK(CanRemove(world.GetEcsWorld(), toRemove));
|
||||
}
|
||||
}
|
||||
167
tests/Util/test_Grid8x8.cpp
Normal file
167
tests/Util/test_Grid8x8.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#include <doctest/doctest.h>
|
||||
#include <vector>
|
||||
#include "Types/Grid8x8.h"
|
||||
|
||||
TEST_SUITE("Grid8x8")
|
||||
{
|
||||
TEST_CASE("default construction - all cells empty")
|
||||
{
|
||||
Grid8x8 g;
|
||||
CHECK(g.Bits == 0);
|
||||
CHECK(g.Count() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Set and Get single cell")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(3, 5);
|
||||
CHECK(g.Get(3, 5));
|
||||
CHECK_FALSE(g.Get(0, 0));
|
||||
CHECK_FALSE(g.Get(3, 4));
|
||||
CHECK_FALSE(g.Get(4, 5));
|
||||
}
|
||||
|
||||
TEST_CASE("Clear cell")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(2, 2);
|
||||
CHECK(g.Get(2, 2));
|
||||
g.Clear(2, 2);
|
||||
CHECK_FALSE(g.Get(2, 2));
|
||||
CHECK(g.Count() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Set does not affect other cells")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(0, 0);
|
||||
g.Set(7, 7);
|
||||
CHECK(g.Get(0, 0));
|
||||
CHECK(g.Get(7, 7));
|
||||
CHECK_FALSE(g.Get(0, 7));
|
||||
CHECK_FALSE(g.Get(7, 0));
|
||||
CHECK(g.Count() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Count matches number of set cells")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(0, 0);
|
||||
g.Set(1, 0);
|
||||
g.Set(0, 1);
|
||||
CHECK(g.Count() == 3);
|
||||
g.Clear(1, 0);
|
||||
CHECK(g.Count() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Set same cell twice does not increase Count")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(4, 4);
|
||||
g.Set(4, 4);
|
||||
CHECK(g.Count() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("iterator - empty grid yields no cells")
|
||||
{
|
||||
Grid8x8 g;
|
||||
int count = 0;
|
||||
for (auto cell : g)
|
||||
++count;
|
||||
CHECK(count == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("iterator - single cell")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(3, 6);
|
||||
|
||||
std::vector<Grid8x8::Cell> cells;
|
||||
for (auto cell : g)
|
||||
cells.push_back(cell);
|
||||
|
||||
REQUIRE(cells.size() == 1);
|
||||
CHECK(cells[0].X == 3);
|
||||
CHECK(cells[0].Y == 6);
|
||||
}
|
||||
|
||||
TEST_CASE("iterator - corners")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(0, 0);
|
||||
g.Set(7, 0);
|
||||
g.Set(0, 7);
|
||||
g.Set(7, 7);
|
||||
|
||||
std::vector<Grid8x8::Cell> cells;
|
||||
for (auto cell : g)
|
||||
cells.push_back(cell);
|
||||
|
||||
REQUIRE(cells.size() == 4);
|
||||
|
||||
// Iterator visits in bit-index order (row-major: left-to-right, top-to-bottom)
|
||||
CHECK(cells[0].X == 0); CHECK(cells[0].Y == 0);
|
||||
CHECK(cells[1].X == 7); CHECK(cells[1].Y == 0);
|
||||
CHECK(cells[2].X == 0); CHECK(cells[2].Y == 7);
|
||||
CHECK(cells[3].X == 7); CHECK(cells[3].Y == 7);
|
||||
}
|
||||
|
||||
TEST_CASE("iterator - full grid visits all 64 cells")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Bits = ~uint64_t(0);
|
||||
|
||||
int count = 0;
|
||||
bool seen[8][8] = {};
|
||||
for (auto cell : g)
|
||||
{
|
||||
CHECK(cell.X >= 0); CHECK(cell.X < 8);
|
||||
CHECK(cell.Y >= 0); CHECK(cell.Y < 8);
|
||||
CHECK_FALSE(seen[cell.Y][cell.X]);
|
||||
seen[cell.Y][cell.X] = true;
|
||||
++count;
|
||||
}
|
||||
CHECK(count == 64);
|
||||
}
|
||||
|
||||
TEST_CASE("iterator count matches Count()")
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(1, 2);
|
||||
g.Set(3, 4);
|
||||
g.Set(5, 6);
|
||||
g.Set(7, 1);
|
||||
|
||||
int iterated = 0;
|
||||
for (auto cell : g)
|
||||
++iterated;
|
||||
|
||||
CHECK(iterated == g.Count());
|
||||
}
|
||||
|
||||
TEST_CASE("iterator - coordinates decode correctly for every cell")
|
||||
{
|
||||
for (int y = 0; y < 8; ++y)
|
||||
{
|
||||
for (int x = 0; x < 8; ++x)
|
||||
{
|
||||
Grid8x8 g;
|
||||
g.Set(x, y);
|
||||
|
||||
int count = 0;
|
||||
for (auto cell : g)
|
||||
{
|
||||
CHECK(cell.X == x);
|
||||
CHECK(cell.Y == y);
|
||||
++count;
|
||||
}
|
||||
CHECK(count == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("size is 8 bytes")
|
||||
{
|
||||
static_assert(sizeof(Grid8x8) == 8);
|
||||
}
|
||||
}
|
||||
44
tools/node-editor/CMakeLists.txt
Normal file
44
tools/node-editor/CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
# ── ImGui static library ──────────────────────────────────────────────────────
|
||||
|
||||
add_library(imgui_lib STATIC
|
||||
${imgui_SOURCE_DIR}/imgui.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_draw.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_tables.cpp
|
||||
${imgui_SOURCE_DIR}/imgui_widgets.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp
|
||||
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
target_include_directories(imgui_lib PUBLIC
|
||||
${imgui_SOURCE_DIR}
|
||||
${imgui_SOURCE_DIR}/backends
|
||||
)
|
||||
|
||||
target_link_libraries(imgui_lib PUBLIC glfw OpenGL::GL)
|
||||
target_compile_definitions(imgui_lib PUBLIC IMGUI_DEFINE_MATH_OPERATORS)
|
||||
|
||||
# ── imnodes static library ────────────────────────────────────────────────────
|
||||
|
||||
add_library(imnodes_lib STATIC
|
||||
${imnodes_SOURCE_DIR}/imnodes.cpp
|
||||
)
|
||||
|
||||
target_include_directories(imnodes_lib PUBLIC
|
||||
${imnodes_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(imnodes_lib PUBLIC imgui_lib)
|
||||
|
||||
# ── Node editor executable ────────────────────────────────────────────────────
|
||||
|
||||
add_executable(node-editor
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(node-editor PRIVATE
|
||||
factory-hole-core
|
||||
imgui_lib
|
||||
imnodes_lib
|
||||
)
|
||||
96
tools/node-editor/README.md
Normal file
96
tools/node-editor/README.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# World Graph Node Editor
|
||||
|
||||
A standalone visual node editor for authoring world generation graphs. Built with Dear ImGui and imnodes, styled after Blender's geometry nodes.
|
||||
|
||||
## What it does
|
||||
|
||||
Graphs describe how tiles are generated for each chunk in the world. Each node is a mathematical or contextual operation — noise functions, arithmetic, comparisons, tile queries — and they connect together into a tree that evaluates to a float value per tile. The game uses this value to decide what tile type to place.
|
||||
|
||||
Graphs are saved as Flecs JSON (`graph.json`) and loaded by the game at runtime, where they are compiled into an efficient memory-pooled runtime graph and evaluated per-tile during chunk generation.
|
||||
|
||||
## Controls
|
||||
|
||||
| Action | How |
|
||||
|--------|-----|
|
||||
| Add a node | **Add Node** menu in the menu bar |
|
||||
| Connect nodes | Drag from an output pin to an input pin |
|
||||
| Disconnect a link | Right-click the link |
|
||||
| Pan the canvas | Middle-click drag, or Alt + left-click drag |
|
||||
| Zoom | Scroll wheel |
|
||||
| Mark output node | Right-click a node → **Set as Output** |
|
||||
| Delete a node | Right-click a node → **Delete Node** |
|
||||
| Save graph | **File → Save** or **Ctrl+S** (writes `graph.json`) |
|
||||
| Load graph | **File → Load** or **Ctrl+O** (reads `graph.json`) |
|
||||
|
||||
The **output node** (highlighted in orange) is the root of evaluation — it must be set before the graph can be compiled by the game.
|
||||
|
||||
## Node types
|
||||
|
||||
### Math (binary, float → float)
|
||||
`Add` `Subtract` `Multiply` `Divide` `Modulo` `Pow` `Max` `Min`
|
||||
|
||||
### Math (unary, float → float)
|
||||
`Negate` `Abs` `Ceil` `Floor` `Sin` `Cos` `Tan` `Exp` `Log` `Square` `Round` `OneMinus`
|
||||
|
||||
### Math (ternary, float → float)
|
||||
`Lerp` `Clamp`
|
||||
|
||||
### Comparison (float → bool)
|
||||
`Equal` `Smaller` `Greater` `SmallerEqual` `GreaterEqual`
|
||||
|
||||
### Logic (bool → bool)
|
||||
`And` `Or`
|
||||
|
||||
### Control flow
|
||||
`Branch` — inputs: condition (bool), true value (float), false value (float)
|
||||
|
||||
### Noise (no inputs, float output)
|
||||
`Simplex` `OpenSimplex` `Perlin` `Value` `ValueCubic`
|
||||
|
||||
Parameters: **Frequency** — controls the scale of the noise.
|
||||
|
||||
### Leaf nodes
|
||||
| Node | Parameters | Output |
|
||||
|------|-----------|--------|
|
||||
| `Constant` | Value (float) | The constant value |
|
||||
| `IsTile` | RelativeX, RelativeY, TileType | bool — true if the tile at the offset matches the type |
|
||||
| `TileDistance` | Range (1–3), TileType | float — distance to the nearest tile of that type |
|
||||
|
||||
## Graph file format
|
||||
|
||||
Graphs are stored as Flecs JSON. Example of a simple graph that outputs Perlin noise:
|
||||
|
||||
```json
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"name": "Graph",
|
||||
"children": [
|
||||
{
|
||||
"name": "Perlin_0",
|
||||
"components": {
|
||||
"VisualNodeType": {"kind": "Perlin"},
|
||||
"VisualNodePos": {"x": 100.0, "y": 100.0},
|
||||
"NodeParam_Noise": {"frequency": 0.01},
|
||||
"VisualNodeOutput": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Connections between nodes are stored as Flecs relationships (`InputPin0`, `InputPin1`, `InputPin2`) on the destination node, referencing the source node by name. This makes the JSON human-readable and easy to hand-edit.
|
||||
|
||||
## Building
|
||||
|
||||
Built automatically as part of the main CMake project:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build --target node-editor
|
||||
./build/tools/node-editor/node-editor
|
||||
```
|
||||
|
||||
Dependencies (fetched automatically via FetchContent): GLFW, Dear ImGui, imnodes.
|
||||
492
tools/node-editor/main.cpp
Normal file
492
tools/node-editor/main.cpp
Normal file
@@ -0,0 +1,492 @@
|
||||
// World Graph Node Editor
|
||||
// A standalone Dear ImGui + imnodes tool for editing WorldGraph node graphs.
|
||||
// Graphs are saved/loaded as Flecs JSON (graph.json in the working directory).
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include "imnodes.h"
|
||||
|
||||
#include "flecs.h"
|
||||
#include "Components/WorldGraph.hpp"
|
||||
#include "Types/WorldGraph/WorldGraph.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
|
||||
// ─── Editor state ─────────────────────────────────────────────────────────────
|
||||
|
||||
struct EditorNode
|
||||
{
|
||||
int nodeId; // imnodes node ID
|
||||
flecs::entity entity;
|
||||
VisualNodeKind kind;
|
||||
bool isOutput;
|
||||
};
|
||||
|
||||
struct EditorLink
|
||||
{
|
||||
int linkId;
|
||||
int srcAttrId; // output attribute of source node
|
||||
int dstAttrId; // input attribute (pin slot) of destination node
|
||||
};
|
||||
|
||||
// Attribute ID encoding:
|
||||
// node's output pin = nodeId * 10 + 9
|
||||
// node's input pin N = nodeId * 10 + N (N = 0,1,2)
|
||||
|
||||
static int OutputAttr(int nodeId) { return nodeId * 10 + 9; }
|
||||
static int InputAttr (int nodeId, int slot) { return nodeId * 10 + slot; }
|
||||
static int AttrToNode(int attr) { return attr / 10; }
|
||||
static int AttrToSlot(int attr) { return attr % 10; } // 9 = output
|
||||
|
||||
// ─── Kind metadata ────────────────────────────────────────────────────────────
|
||||
|
||||
struct KindInfo { const char* name; int inputCount; bool hasParamConstant; bool hasParamNoise; bool hasParamIsTile; bool hasParamTileDistance; };
|
||||
|
||||
static const KindInfo KindInfoTable[] = {
|
||||
// name ins const noise isTile tileDist
|
||||
{"Add", 2, 0, 0, 0, 0},
|
||||
{"Subtract", 2, 0, 0, 0, 0},
|
||||
{"Multiply", 2, 0, 0, 0, 0},
|
||||
{"Divide", 2, 0, 0, 0, 0},
|
||||
{"Modulo", 2, 0, 0, 0, 0},
|
||||
{"Pow", 2, 0, 0, 0, 0},
|
||||
{"Max", 2, 0, 0, 0, 0},
|
||||
{"Min", 2, 0, 0, 0, 0},
|
||||
{"Negate", 1, 0, 0, 0, 0},
|
||||
{"Abs", 1, 0, 0, 0, 0},
|
||||
{"Ceil", 1, 0, 0, 0, 0},
|
||||
{"Floor", 1, 0, 0, 0, 0},
|
||||
{"Sin", 1, 0, 0, 0, 0},
|
||||
{"Cos", 1, 0, 0, 0, 0},
|
||||
{"Tan", 1, 0, 0, 0, 0},
|
||||
{"Exp", 1, 0, 0, 0, 0},
|
||||
{"Log", 1, 0, 0, 0, 0},
|
||||
{"Square", 1, 0, 0, 0, 0},
|
||||
{"Round", 1, 0, 0, 0, 0},
|
||||
{"OneMinus", 1, 0, 0, 0, 0},
|
||||
{"Lerp", 3, 0, 0, 0, 0},
|
||||
{"Clamp", 3, 0, 0, 0, 0},
|
||||
{"Equal", 2, 0, 0, 0, 0},
|
||||
{"Smaller", 2, 0, 0, 0, 0},
|
||||
{"Greater", 2, 0, 0, 0, 0},
|
||||
{"SmallerEqual", 2, 0, 0, 0, 0},
|
||||
{"GreaterEqual", 2, 0, 0, 0, 0},
|
||||
{"And", 2, 0, 0, 0, 0},
|
||||
{"Or", 2, 0, 0, 0, 0},
|
||||
{"Branch", 3, 0, 0, 0, 0},
|
||||
{"Simplex", 0, 0, 1, 0, 0},
|
||||
{"OpenSimplex", 0, 0, 1, 0, 0},
|
||||
{"Perlin", 0, 0, 1, 0, 0},
|
||||
{"Value", 0, 0, 1, 0, 0},
|
||||
{"ValueCubic", 0, 0, 1, 0, 0},
|
||||
{"Constant", 0, 1, 0, 0, 0},
|
||||
{"IsTile", 0, 0, 0, 1, 0},
|
||||
{"TileDistance", 0, 0, 0, 0, 1},
|
||||
};
|
||||
|
||||
static const KindInfo& GetKindInfo(VisualNodeKind k)
|
||||
{
|
||||
return KindInfoTable[static_cast<int>(k)];
|
||||
}
|
||||
|
||||
// ─── Application ─────────────────────────────────────────────────────────────
|
||||
|
||||
class NodeEditorApp
|
||||
{
|
||||
public:
|
||||
NodeEditorApp()
|
||||
{
|
||||
Flecs_WorldGraph(World);
|
||||
GraphRoot = World.entity("Graph");
|
||||
}
|
||||
|
||||
// ── Graph I/O ─────────────────────────────────────────────────────────────
|
||||
|
||||
void Save(const char* path)
|
||||
{
|
||||
// Sync editor positions back to components before saving
|
||||
for (auto& n : Nodes)
|
||||
{
|
||||
ImVec2 pos = ImNodes::GetNodeGridSpacePos(n.nodeId);
|
||||
n.entity.set<VisualNodePos>({pos.x, pos.y});
|
||||
}
|
||||
char* json = ecs_world_to_json(World.c_ptr(), nullptr);
|
||||
std::ofstream f(path);
|
||||
f << json;
|
||||
ecs_os_free(json);
|
||||
printf("Saved to %s\n", path);
|
||||
}
|
||||
|
||||
void Load(const char* path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f) { printf("Could not open %s\n", path); return; }
|
||||
std::ostringstream ss; ss << f.rdbuf();
|
||||
std::string json = ss.str();
|
||||
ecs_world_from_json(World.c_ptr(), json.c_str(), nullptr);
|
||||
RebuildEditorState();
|
||||
printf("Loaded from %s\n", path);
|
||||
}
|
||||
|
||||
// ── Rebuild editor state from Flecs world ─────────────────────────────────
|
||||
|
||||
void RebuildEditorState()
|
||||
{
|
||||
Nodes.clear();
|
||||
Links.clear();
|
||||
NextId = 1;
|
||||
|
||||
// Map entity → nodeId
|
||||
std::unordered_map<uint64_t, int> entityToNode;
|
||||
|
||||
World.query_builder<VisualNodeType>()
|
||||
.with(flecs::ChildOf, GraphRoot)
|
||||
.build()
|
||||
.each([&](flecs::entity e, VisualNodeType& t)
|
||||
{
|
||||
int nid = NextId++;
|
||||
entityToNode[e.id()] = nid;
|
||||
Nodes.push_back({ nid, e, t.kind, e.has<VisualNodeOutput>() });
|
||||
|
||||
// Restore canvas position
|
||||
const VisualNodePos* pos = e.try_get<VisualNodePos>();
|
||||
if (pos) ImNodes::SetNodeGridSpacePos(nid, ImVec2{pos->x, pos->y});
|
||||
});
|
||||
|
||||
// Rebuild links
|
||||
for (auto& n : Nodes)
|
||||
{
|
||||
auto addLink = [&](int slot, flecs::entity src)
|
||||
{
|
||||
if (!src) return;
|
||||
auto it = entityToNode.find(src.id());
|
||||
if (it == entityToNode.end()) return;
|
||||
int srcNodeId = it->second;
|
||||
Links.push_back({ NextId++,
|
||||
OutputAttr(srcNodeId),
|
||||
InputAttr(n.nodeId, slot) });
|
||||
};
|
||||
addLink(0, n.entity.target<InputPin0>());
|
||||
addLink(1, n.entity.target<InputPin1>());
|
||||
addLink(2, n.entity.target<InputPin2>());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Add a new node ────────────────────────────────────────────────────────
|
||||
|
||||
void AddNode(VisualNodeKind kind)
|
||||
{
|
||||
static int nameCounter = 0;
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "%s_%d", GetKindInfo(kind).name, nameCounter++);
|
||||
|
||||
flecs::entity e = World.entity(name)
|
||||
.child_of(GraphRoot)
|
||||
.set<VisualNodeType>({ kind })
|
||||
.set<VisualNodePos>({ 100.f, 100.f });
|
||||
|
||||
// Add default parameters
|
||||
if (GetKindInfo(kind).hasParamConstant) e.set<NodeParam_Constant>({ 0.f });
|
||||
if (GetKindInfo(kind).hasParamNoise) e.set<NodeParam_Noise>({ 0.01f });
|
||||
if (GetKindInfo(kind).hasParamIsTile) e.set<NodeParam_IsTile>({ 0, 0, TileType::Air });
|
||||
if (GetKindInfo(kind).hasParamTileDistance)e.set<NodeParam_TileDistance>({ 1, TileType::Air });
|
||||
|
||||
int nid = NextId++;
|
||||
Nodes.push_back({ nid, e, kind, false });
|
||||
ImNodes::SetNodeGridSpacePos(nid, { 100.f, 100.f });
|
||||
}
|
||||
|
||||
// ── Render one frame ──────────────────────────────────────────────────────
|
||||
|
||||
void Render()
|
||||
{
|
||||
// ── Menu bar ─────────────────────────────────────────────────────────
|
||||
if (ImGui::BeginMainMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem("Save", "Ctrl+S")) Save("graph.json");
|
||||
if (ImGui::MenuItem("Load", "Ctrl+O")) Load("graph.json");
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Add Node"))
|
||||
{
|
||||
for (int i = 0; i <= (int)VisualNodeKind::TileDistance; ++i)
|
||||
{
|
||||
if (ImGui::MenuItem(KindInfoTable[i].name))
|
||||
AddNode(static_cast<VisualNodeKind>(i));
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
// ── Full-screen canvas ────────────────────────────────────────────────
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::SetNextWindowPos({0, ImGui::GetFrameHeight()});
|
||||
ImGui::SetNextWindowSize({io.DisplaySize.x, io.DisplaySize.y - ImGui::GetFrameHeight()});
|
||||
ImGui::Begin("##canvas", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove);
|
||||
|
||||
ImNodes::BeginNodeEditor();
|
||||
|
||||
// ── Draw nodes ────────────────────────────────────────────────────────
|
||||
for (auto& n : Nodes)
|
||||
{
|
||||
const KindInfo& info = GetKindInfo(n.kind);
|
||||
|
||||
if (n.isOutput)
|
||||
ImNodes::PushColorStyle(ImNodesCol_TitleBar, IM_COL32(150, 80, 20, 255));
|
||||
|
||||
ImNodes::BeginNode(n.nodeId);
|
||||
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted(info.name);
|
||||
if (n.isOutput) ImGui::SameLine(); if (n.isOutput) ImGui::TextUnformatted(" [OUT]");
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
// Input pins
|
||||
static const char* slotLabels[] = { "In 0", "In 1", "In 2" };
|
||||
for (int s = 0; s < info.inputCount; ++s)
|
||||
{
|
||||
ImNodes::BeginInputAttribute(InputAttr(n.nodeId, s));
|
||||
ImGui::TextUnformatted(slotLabels[s]);
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
// Inline parameters
|
||||
ImGui::PushItemWidth(120.f);
|
||||
if (info.hasParamConstant)
|
||||
{
|
||||
auto* p = n.entity.try_get_mut<NodeParam_Constant>();
|
||||
ImGui::DragFloat("##val", &p->value, 0.01f);
|
||||
}
|
||||
if (info.hasParamNoise)
|
||||
{
|
||||
auto* p = n.entity.try_get_mut<NodeParam_Noise>();
|
||||
ImGui::DragFloat("Freq##", &p->frequency, 0.001f, 0.0001f, 10.f);
|
||||
}
|
||||
if (info.hasParamIsTile)
|
||||
{
|
||||
auto* p = n.entity.try_get_mut<NodeParam_IsTile>();
|
||||
ImGui::DragInt("RelX##", reinterpret_cast<int*>(&p->relativeX), 1.f, -4, 4);
|
||||
ImGui::DragInt("RelY##", reinterpret_cast<int*>(&p->relativeY), 1.f, -4, 4);
|
||||
int t = static_cast<int>(p->tileType);
|
||||
if (ImGui::Combo("Type##", &t, "Air\0Filler\0Liquid\0Ore\0NPC\0Plant\0"))
|
||||
p->tileType = static_cast<TileType>(t);
|
||||
}
|
||||
if (info.hasParamTileDistance)
|
||||
{
|
||||
auto* p = n.entity.try_get_mut<NodeParam_TileDistance>();
|
||||
int r = p->range;
|
||||
if (ImGui::SliderInt("Range##", &r, 1, 3)) p->range = static_cast<int8_t>(r);
|
||||
int t = static_cast<int>(p->tileType);
|
||||
if (ImGui::Combo("Type##td", &t, "Air\0Filler\0Liquid\0Ore\0NPC\0Plant\0"))
|
||||
p->tileType = static_cast<TileType>(t);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
// Output pin
|
||||
ImNodes::BeginOutputAttribute(OutputAttr(n.nodeId));
|
||||
ImGui::TextUnformatted("Out");
|
||||
ImNodes::EndOutputAttribute();
|
||||
|
||||
ImNodes::EndNode();
|
||||
|
||||
if (n.isOutput)
|
||||
ImNodes::PopColorStyle();
|
||||
}
|
||||
|
||||
// ── Draw links ────────────────────────────────────────────────────────
|
||||
for (auto& l : Links)
|
||||
ImNodes::Link(l.linkId, l.srcAttrId, l.dstAttrId);
|
||||
|
||||
ImNodes::EndNodeEditor();
|
||||
|
||||
// ── Handle new link ───────────────────────────────────────────────────
|
||||
{
|
||||
int startAttr, endAttr;
|
||||
if (ImNodes::IsLinkCreated(&startAttr, &endAttr))
|
||||
{
|
||||
// Normalise: output attr should be startAttr
|
||||
if (AttrToSlot(startAttr) != 9) std::swap(startAttr, endAttr);
|
||||
if (AttrToSlot(startAttr) == 9 && AttrToSlot(endAttr) != 9)
|
||||
{
|
||||
int srcNodeId = AttrToNode(startAttr);
|
||||
int dstNodeId = AttrToNode(endAttr);
|
||||
int slot = AttrToSlot(endAttr);
|
||||
|
||||
auto srcIt = std::find_if(Nodes.begin(), Nodes.end(), [&](auto& n){ return n.nodeId == srcNodeId; });
|
||||
auto dstIt = std::find_if(Nodes.begin(), Nodes.end(), [&](auto& n){ return n.nodeId == dstNodeId; });
|
||||
|
||||
if (srcIt != Nodes.end() && dstIt != Nodes.end())
|
||||
{
|
||||
flecs::entity src = srcIt->entity;
|
||||
flecs::entity dst = dstIt->entity;
|
||||
|
||||
// Remove existing connection on this slot
|
||||
if (slot == 0) dst.remove<InputPin0>(flecs::Wildcard);
|
||||
if (slot == 1) dst.remove<InputPin1>(flecs::Wildcard);
|
||||
if (slot == 2) dst.remove<InputPin2>(flecs::Wildcard);
|
||||
|
||||
// Add new connection
|
||||
if (slot == 0) dst.add<InputPin0>(src);
|
||||
if (slot == 1) dst.add<InputPin1>(src);
|
||||
if (slot == 2) dst.add<InputPin2>(src);
|
||||
|
||||
Links.push_back({ NextId++, startAttr, endAttr });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Handle link deletion ──────────────────────────────────────────────
|
||||
{
|
||||
int linkId;
|
||||
if (ImNodes::IsLinkDestroyed(&linkId))
|
||||
{
|
||||
auto it = std::find_if(Links.begin(), Links.end(), [&](auto& l){ return l.linkId == linkId; });
|
||||
if (it != Links.end())
|
||||
{
|
||||
int dstNodeId = AttrToNode(it->dstAttrId);
|
||||
int slot = AttrToSlot(it->dstAttrId);
|
||||
auto dstIt = std::find_if(Nodes.begin(), Nodes.end(), [&](auto& n){ return n.nodeId == dstNodeId; });
|
||||
if (dstIt != Nodes.end())
|
||||
{
|
||||
if (slot == 0) dstIt->entity.remove<InputPin0>(flecs::Wildcard);
|
||||
if (slot == 1) dstIt->entity.remove<InputPin1>(flecs::Wildcard);
|
||||
if (slot == 2) dstIt->entity.remove<InputPin2>(flecs::Wildcard);
|
||||
}
|
||||
Links.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Context menu: mark output / delete ───────────────────────────────
|
||||
{
|
||||
int hoveredNode = -1;
|
||||
if (ImNodes::IsNodeHovered(&hoveredNode) && ImGui::IsMouseClicked(ImGuiMouseButton_Right))
|
||||
{
|
||||
ContextMenuNodeId = hoveredNode;
|
||||
ImGui::OpenPopup("node_ctx");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("node_ctx"))
|
||||
{
|
||||
int nodeId = ContextMenuNodeId;
|
||||
auto it = std::find_if(Nodes.begin(), Nodes.end(), [&](auto& n){ return n.nodeId == nodeId; });
|
||||
if (it != Nodes.end())
|
||||
{
|
||||
if (ImGui::MenuItem("Set as Output"))
|
||||
{
|
||||
for (auto& n : Nodes) { n.isOutput = false; n.entity.remove<VisualNodeOutput>(); }
|
||||
it->isOutput = true;
|
||||
it->entity.add<VisualNodeOutput>();
|
||||
}
|
||||
if (ImGui::MenuItem("Delete Node"))
|
||||
{
|
||||
// Remove all links connected to this node
|
||||
Links.erase(std::remove_if(Links.begin(), Links.end(), [&](auto& l)
|
||||
{
|
||||
return AttrToNode(l.srcAttrId) == nodeId || AttrToNode(l.dstAttrId) == nodeId;
|
||||
}), Links.end());
|
||||
it->entity.destruct();
|
||||
Nodes.erase(it);
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
flecs::world World;
|
||||
flecs::entity GraphRoot;
|
||||
std::vector<EditorNode> Nodes;
|
||||
std::vector<EditorLink> Links;
|
||||
int NextId = 1;
|
||||
int ContextMenuNodeId = -1;
|
||||
};
|
||||
|
||||
// ─── GLFW error callback ──────────────────────────────────────────────────────
|
||||
|
||||
static void GLFWErrorCallback(int error, const char* desc)
|
||||
{
|
||||
fprintf(stderr, "GLFW error %d: %s\n", error, desc);
|
||||
}
|
||||
|
||||
// ─── main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
int main()
|
||||
{
|
||||
glfwSetErrorCallback(GLFWErrorCallback);
|
||||
if (!glfwInit()) return 1;
|
||||
|
||||
const char* glsl_version = "#version 330";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
GLFWwindow* window = glfwCreateWindow(1280, 720, "World Graph Editor", nullptr, nullptr);
|
||||
if (!window) { glfwTerminate(); return 1; }
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(1);
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImNodes::CreateContext();
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
ImNodes::StyleColorsDark();
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
NodeEditorApp app;
|
||||
|
||||
while (!glfwWindowShouldClose(window))
|
||||
{
|
||||
glfwPollEvents();
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Ctrl+S / Ctrl+O shortcuts
|
||||
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl))
|
||||
{
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_S)) app.Save("graph.json");
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_O)) app.Load("graph.json");
|
||||
}
|
||||
|
||||
app.Render();
|
||||
|
||||
ImGui::Render();
|
||||
int w, h;
|
||||
glfwGetFramebufferSize(window, &w, &h);
|
||||
glViewport(0, 0, w, h);
|
||||
glClearColor(0.15f, 0.15f, 0.15f, 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImNodes::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user