WIP VoxelGeneratorGraph, compiled and untested

master
Marc Gilleron 2020-02-19 19:26:14 +00:00
parent e3e24082af
commit 515751a3e6
7 changed files with 1072 additions and 2 deletions

1
SCsub
View File

@ -12,6 +12,7 @@ files = [
"meshers/*.cpp",
"streams/*.cpp",
"generators/*.cpp",
"generators/graph/*.cpp",
"util/*.cpp",
"terrain/*.cpp",
"math/*.cpp",

View File

@ -38,8 +38,7 @@ def get_doc_classes():
"VoxelMesherBlocky",
"VoxelMesherTransvoxel",
"VoxelMesherDMC"
]
]
def get_doc_path():

View File

@ -0,0 +1,241 @@
#include "program_graph.h"
#include <core/os/file_access.h>
#include <core/variant.h>
#include <unordered_set>
template <typename T>
inline bool range_contains(const std::vector<T> &vec, const T &v, uint32_t begin, uint32_t end) {
CRASH_COND(end > vec.size());
for (size_t i = begin; i < end; ++i) {
if (vec[i] == v) {
return true;
}
}
return false;
}
uint32_t ProgramGraph::Node::find_input_connection(PortLocation src, uint32_t input_port_index) const {
CRASH_COND(input_port_index >= inputs.size());
const Port &p = inputs[input_port_index];
for (size_t i = 0; i < p.connections.size(); ++i) {
if (p.connections[i] == src) {
return i;
}
}
return ProgramGraph::NULL_INDEX;
}
uint32_t ProgramGraph::Node::find_output_connection(uint32_t output_port_index, PortLocation dst) const {
CRASH_COND(output_port_index >= outputs.size());
const Port &p = outputs[output_port_index];
for (size_t i = 0; i < p.connections.size(); ++i) {
if (p.connections[i] == dst) {
return i;
}
}
return ProgramGraph::NULL_INDEX;
}
ProgramGraph::Node *ProgramGraph::create_node() {
Node *node = memnew(Node);
node->id = _next_node_id++;
_nodes[node->id] = node;
return node;
}
void ProgramGraph::remove_node(uint32_t node_id) {
Node *node = get_node(node_id);
// Remove input connections
for (uint32_t dst_port_index = 0; dst_port_index < node->inputs.size(); ++dst_port_index) {
const Port &p = node->inputs[dst_port_index];
for (auto it = p.connections.begin(); it != p.connections.end(); ++it) {
const PortLocation src = *it;
Node *src_node = get_node(src.node_id);
uint32_t i = src_node->find_output_connection(src.port_index, PortLocation{ node_id, dst_port_index });
CRASH_COND(i == NULL_INDEX);
std::vector<PortLocation> &connections = src_node->outputs[src.port_index].connections;
connections.erase(connections.begin() + i);
}
}
// Remove output connections
for (uint32_t src_port_index = 0; src_port_index < node->outputs.size(); ++src_port_index) {
const Port &p = node->outputs[src_port_index];
for (auto it = p.connections.begin(); it != p.connections.end(); ++it) {
const PortLocation dst = *it;
Node *dst_node = get_node(dst.node_id);
uint32_t i = dst_node->find_input_connection(PortLocation{ node_id, src_port_index }, dst.port_index);
CRASH_COND(i == NULL_INDEX);
std::vector<PortLocation> &connections = dst_node->inputs[dst.port_index].connections;
connections.erase(connections.begin() + i);
}
}
_nodes.erase(node_id);
memdelete(node);
}
void ProgramGraph::clear() {
for (auto it = _nodes.begin(); it != _nodes.end(); ++it) {
Node *node = it->second;
CRASH_COND(node == nullptr);
memdelete(node);
}
_nodes.clear();
}
bool ProgramGraph::is_connected(PortLocation src, PortLocation dst) const {
Node *src_node = get_node(src.node_id);
Node *dst_node = get_node(dst.node_id);
if (src_node->find_output_connection(src.port_index, dst) != NULL_INDEX) {
CRASH_COND(dst_node->find_input_connection(src, dst.port_index) == NULL_INDEX);
return true;
} else {
CRASH_COND(dst_node->find_input_connection(src, dst.port_index) != NULL_INDEX);
return false;
}
}
void ProgramGraph::connect(PortLocation src, PortLocation dst) {
CRASH_COND(is_connected(src, dst));
CRASH_COND(has_path(dst.node_id, src.node_id));
Node *src_node = get_node(src.node_id);
Node *dst_node = get_node(dst.node_id);
CRASH_COND(dst_node->inputs[dst.port_index].connections.size() != 0);
src_node->outputs[src.port_index].connections.push_back(dst);
dst_node->inputs[dst.port_index].connections.push_back(src);
}
bool ProgramGraph::disconnect(PortLocation src, PortLocation dst) {
Node *src_node = get_node(src.node_id);
Node *dst_node = get_node(dst.node_id);
uint32_t src_i = src_node->find_output_connection(src.port_index, dst);
if (src_i == NULL_INDEX) {
return false;
}
uint32_t dst_i = dst_node->find_input_connection(src, dst.port_index);
CRASH_COND(dst_i == NULL_INDEX);
std::vector<PortLocation> &src_connections = src_node->outputs[src.port_index].connections;
std::vector<PortLocation> &dst_connections = dst_node->inputs[dst.port_index].connections;
src_connections.erase(src_connections.begin() + src_i);
dst_connections.erase(dst_connections.begin() + dst_i);
return true;
}
ProgramGraph::Node *ProgramGraph::get_node(uint32_t id) const {
auto it = _nodes.find(id);
CRASH_COND(it == _nodes.end());
Node *node = it->second;
CRASH_COND(node == nullptr);
return node;
}
bool ProgramGraph::has_path(uint32_t p_src_node_id, uint32_t p_dst_node_id) const {
std::vector<uint32_t> nodes_to_process;
std::unordered_set<uint32_t> visited_nodes;
nodes_to_process.push_back(p_src_node_id);
while (nodes_to_process.size() > 0) {
const Node *node = get_node(nodes_to_process.back());
nodes_to_process.pop_back();
visited_nodes.insert(node->id);
uint32_t nodes_to_process_begin = nodes_to_process.size();
// Find destinations
for (uint32_t oi = 0; oi < node->outputs.size(); ++oi) {
const Port &p = node->outputs[oi];
for (auto cit = p.connections.begin(); cit != p.connections.end(); ++cit) {
PortLocation dst = *cit;
if (dst.node_id == p_dst_node_id) {
return true;
}
// A node can have two connections to the same destination node
if (range_contains(nodes_to_process, dst.node_id, nodes_to_process_begin, nodes_to_process.size())) {
continue;
}
if (visited_nodes.find(dst.node_id) != visited_nodes.end()) {
continue;
}
nodes_to_process.push_back(dst.node_id);
}
}
}
return false;
}
void ProgramGraph::evaluate(std::vector<uint32_t> &order) const {
std::vector<uint32_t> nodes_to_process;
std::unordered_set<uint32_t> visited_nodes;
// Find terminal nodes
for (auto it = _nodes.begin(); it != _nodes.end(); ++it) {
const Node *node = it->second;
if (node->outputs.size() == 0) {
nodes_to_process.push_back(it->first);
}
}
while (nodes_to_process.size() > 0) {
const Node *node = get_node(nodes_to_process.back());
uint32_t nodes_to_process_begin = nodes_to_process.size();
// Find ancestors
for (uint32_t ii = 0; ii < node->inputs.size(); ++ii) {
const Port &p = node->inputs[ii];
for (auto cit = p.connections.begin(); cit != p.connections.end(); ++cit) {
PortLocation src = *cit;
// A node can have two connections to the same destination node
if (range_contains(nodes_to_process, src.node_id, nodes_to_process_begin, nodes_to_process.size())) {
continue;
}
if (visited_nodes.find(src.node_id) != visited_nodes.end()) {
continue;
}
nodes_to_process.push_back(src.node_id);
}
}
if (nodes_to_process_begin == nodes_to_process.size()) {
// No ancestor to visit, process the node
order.push_back(node->id);
visited_nodes.insert(node->id);
nodes_to_process.pop_back();
}
}
}
void ProgramGraph::debug_print_dot_file(String file_path) const {
// https://www.graphviz.org/pdf/dotguide.pdf
Error err;
FileAccess *f = FileAccess::open(file_path, FileAccess::WRITE, &err);
if (f == nullptr) {
ERR_PRINT(String("Could not write ProgramGraph debug file as {0}: error {1}").format(varray(file_path, err)));
return;
}
f->store_line("digraph G {");
for (auto nit = _nodes.begin(); nit != _nodes.end(); ++nit) {
const Node *node = nit->second;
const uint32_t node_id = nit->first;
for (auto oit = node->outputs.begin(); oit != node->outputs.end(); ++oit) {
const Port &port = *oit;
for (auto cit = port.connections.begin(); cit != port.connections.end(); ++cit) {
PortLocation dst = *cit;
f->store_line(String("\tn{0} -> n{1};").format(varray(node_id, dst.node_id)));
}
}
}
f->store_line("}");
f->close();
memdelete(f);
}

View File

@ -0,0 +1,61 @@
#ifndef PROGRAM_GRAPH_H
#define PROGRAM_GRAPH_H
#include <core/hashfuncs.h>
#include <unordered_map>
#include <vector>
class ProgramGraph {
public:
static const uint32_t NULL_ID = 0;
static const uint32_t NULL_INDEX = -1;
struct PortLocation {
uint32_t node_id;
uint32_t port_index;
};
struct PortLocationHasher {
static inline uint32_t hash(const PortLocation &v) {
uint32_t hash = hash_djb2_one_32(v.node_id);
return hash_djb2_one_32(v.node_id, hash);
}
};
struct Port {
std::vector<PortLocation> connections;
};
struct Node {
uint32_t id;
std::vector<Port> inputs;
std::vector<Port> outputs;
uint32_t find_input_connection(PortLocation src, uint32_t input_port_index) const;
uint32_t find_output_connection(uint32_t output_port_index, PortLocation dst) const;
};
Node *create_node();
Node *get_node(uint32_t id) const;
void remove_node(uint32_t id);
void clear();
bool is_connected(PortLocation src, PortLocation dst) const;
void connect(PortLocation src, PortLocation dst);
bool disconnect(PortLocation src, PortLocation dst);
bool has_path(uint32_t p_src_node_id, uint32_t p_dst_node_id) const;
void evaluate(std::vector<uint32_t> &order) const;
void debug_print_dot_file(String file_path) const;
private:
std::unordered_map<uint32_t, Node *> _nodes;
uint32_t _next_node_id = 1;
};
inline bool operator==(const ProgramGraph::PortLocation &a, const ProgramGraph::PortLocation &b) {
return a.node_id == b.node_id && a.port_index == b.port_index;
}
#endif // PROGRAM_GRAPH_H

View File

@ -0,0 +1,659 @@
#include "voxel_generator_graph.h"
namespace {
VoxelGeneratorGraph::NodeTypeDB *g_node_type_db = nullptr;
}
VoxelGeneratorGraph::NodeTypeDB *VoxelGeneratorGraph::NodeTypeDB::get_singleton() {
CRASH_COND(g_node_type_db == nullptr);
return g_node_type_db;
}
void VoxelGeneratorGraph::NodeTypeDB::create_singleton() {
CRASH_COND(g_node_type_db != nullptr);
g_node_type_db = memnew(NodeTypeDB());
}
void VoxelGeneratorGraph::NodeTypeDB::destroy_singleton() {
CRASH_COND(g_node_type_db == nullptr);
memdelete(g_node_type_db);
g_node_type_db = nullptr;
}
VoxelGeneratorGraph::NodeTypeDB::NodeTypeDB() {
{
NodeType &t = types[VoxelGeneratorGraph::NODE_CONSTANT];
t.outputs.push_back(Port("Value", true));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_INPUT_X];
t.outputs.push_back(Port("Value", true));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_INPUT_Y];
t.outputs.push_back(Port("Value", true));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_INPUT_Z];
t.outputs.push_back(Port("Value", true));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_OUTPUT_SDF];
t.inputs.push_back(Port("Value", true));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_ADD];
t.inputs.push_back(Port("A"));
t.inputs.push_back(Port("B"));
t.outputs.push_back(Port("Sum"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SUBTRACT];
t.inputs.push_back(Port("A"));
t.inputs.push_back(Port("B"));
t.outputs.push_back(Port("Sum"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_MULTIPLY];
t.inputs.push_back(Port("A"));
t.inputs.push_back(Port("B"));
t.outputs.push_back(Port("Product"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SINE];
t.inputs.push_back(Port("X"));
t.outputs.push_back(Port("Result"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_FLOOR];
t.inputs.push_back(Port("X"));
t.outputs.push_back(Port("Result"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_ABS];
t.inputs.push_back(Port("X"));
t.outputs.push_back(Port("Result"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SQRT];
t.inputs.push_back(Port("X"));
t.outputs.push_back(Port("Result"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_DISTANCE_2D];
t.inputs.push_back(Port("X0"));
t.inputs.push_back(Port("Y0"));
t.inputs.push_back(Port("X1"));
t.inputs.push_back(Port("Y1"));
t.outputs.push_back(Port("Result"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_DISTANCE_3D];
t.inputs.push_back(Port("X0"));
t.inputs.push_back(Port("Y0"));
t.inputs.push_back(Port("Y0"));
t.inputs.push_back(Port("X1"));
t.inputs.push_back(Port("Y1"));
t.inputs.push_back(Port("Z1"));
t.outputs.push_back(Port("Result"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_CLAMP];
t.inputs.push_back(Port("X"));
t.outputs.push_back(Port("Result"));
t.params.push_back(Param("Min", -1.0));
t.params.push_back(Param("Max", 1.0));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_MIX];
t.inputs.push_back(Port("A"));
t.inputs.push_back(Port("B"));
t.inputs.push_back(Port("Ratio"));
t.outputs.push_back(Port("Result"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_REMAP];
t.inputs.push_back(Port("X"));
t.outputs.push_back(Port("Result"));
t.params.push_back(Param("Min0", -1.0));
t.params.push_back(Param("Max0", 1.0));
t.params.push_back(Param("Min1", -1.0));
t.params.push_back(Param("Max1", 1.0));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_CURVE];
t.inputs.push_back(Port("X"));
t.outputs.push_back(Port("Result"));
t.params.push_back(Param("Curve"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_NOISE_2D];
t.inputs.push_back(Port("X"));
t.inputs.push_back(Port("Y"));
t.outputs.push_back(Port("Result"));
t.params.push_back(Param("Noise"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_NOISE_3D];
t.inputs.push_back(Port("X"));
t.inputs.push_back(Port("Y"));
t.inputs.push_back(Port("Z"));
t.outputs.push_back(Port("Result"));
t.params.push_back(Param("Noise"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_IMAGE_2D];
t.inputs.push_back(Port("X"));
t.inputs.push_back(Port("Y"));
t.outputs.push_back(Port("Result"));
t.params.push_back(Param("Image"));
}
}
VoxelGeneratorGraph::VoxelGeneratorGraph() {
typedef ProgramGraph::PortLocation PL;
// Default
uint32_t n_x = create_node(NODE_INPUT_X);
uint32_t n_y = create_node(NODE_INPUT_Y);
uint32_t n_z = create_node(NODE_INPUT_Y);
uint32_t n_o = create_node(NODE_OUTPUT_SDF);
uint32_t n_sin0 = create_node(NODE_SINE);
uint32_t n_sin1 = create_node(NODE_SINE);
uint32_t n_add = create_node(NODE_ADD);
uint32_t n_mul0 = create_node(NODE_MULTIPLY);
uint32_t n_mul1 = create_node(NODE_MULTIPLY);
uint32_t n_mul2 = create_node(NODE_MULTIPLY);
uint32_t n_c0 = create_node(NODE_CONSTANT);
uint32_t n_c1 = create_node(NODE_CONSTANT);
uint32_t n_sub = create_node(NODE_SUBTRACT);
node_set_param(n_c0, 0, 1.0 / 20.0);
node_set_param(n_c1, 0, 10.0);
/*
* X --- * --- sin Z
* / \ \
* 1/20 + --- * --- - --- O
* \ / /
* Y --- * --- sin 10.0
*/
node_connect(PL{ n_x, 0 }, PL{ n_mul0, 0 });
node_connect(PL{ n_y, 0 }, PL{ n_mul1, 0 });
node_connect(PL{ n_c0, 0 }, PL{ n_mul0, 1 });
node_connect(PL{ n_c0, 0 }, PL{ n_mul1, 1 });
node_connect(PL{ n_mul0, 0 }, PL{ n_sin0, 0 });
node_connect(PL{ n_mul1, 0 }, PL{ n_sin1, 0 });
node_connect(PL{ n_sin0, 0 }, PL{ n_add, 0 });
node_connect(PL{ n_sin1, 0 }, PL{ n_add, 1 });
node_connect(PL{ n_add, 0 }, PL{ n_mul2, 0 });
node_connect(PL{ n_c1, 0 }, PL{ n_mul2, 1 });
node_connect(PL{ n_z, 0 }, PL{ n_sub, 0 });
node_connect(PL{ n_mul2, 0 }, PL{ n_sub, 1 });
node_connect(PL{ n_sub, 0 }, PL{ n_o, 0 });
_graph.debug_print_dot_file("voxel_graph_test.dot");
}
VoxelGeneratorGraph::~VoxelGeneratorGraph() {
clear();
}
void VoxelGeneratorGraph::clear() {
const uint32_t *key;
while ((key = _nodes.next(key))) {
Node *node = _nodes.get(*key);
CRASH_COND(node == nullptr);
memdelete(node);
}
_nodes.clear();
_graph.clear();
_program.clear();
_memory.clear();
}
uint32_t VoxelGeneratorGraph::create_node(NodeTypeID type_id) {
const NodeTypeDB::NodeType &type = NodeTypeDB::get_singleton()->types[type_id];
ProgramGraph::Node *pg_node = _graph.create_node();
pg_node->inputs.resize(type.inputs.size());
pg_node->outputs.resize(type.outputs.size());
Node *node = memnew(Node);
node->params.resize(type.params.size());
node->type = type_id;
_nodes[pg_node->id] = node;
return pg_node->id;
}
void VoxelGeneratorGraph::remove_node(uint32_t node_id) {
_graph.remove_node(node_id);
Node **pptr = _nodes.getptr(node_id);
if (pptr != nullptr) {
Node *node = *pptr;
memdelete(node);
_nodes.erase(node_id);
}
}
void VoxelGeneratorGraph::node_connect(ProgramGraph::PortLocation src, ProgramGraph::PortLocation dst) {
_graph.connect(src, dst);
}
void VoxelGeneratorGraph::node_set_param(uint32_t node_id, uint32_t param_index, Variant value) {
Node **pptr = _nodes.getptr(node_id);
ERR_FAIL_COND(pptr == nullptr);
Node *node = *pptr;
node->params[param_index] = value;
}
void VoxelGeneratorGraph::generate_block(VoxelBlockRequest &input) {
VoxelBuffer &out_buffer = **input.voxel_buffer;
const Vector3i bs = out_buffer.get_size();
const VoxelBuffer::ChannelId channel = _channel;
Vector3i rpos;
Vector3i gpos;
// Loads of possible optimization from there
int stride = 1 << input.lod;
for (rpos.z = 0, gpos.z = input.origin_in_voxels.z; rpos.z < bs.z; ++rpos.z, gpos.z += stride) {
for (rpos.x = 0, gpos.x = input.origin_in_voxels.x; rpos.x < bs.x; ++rpos.x, gpos.x += stride) {
for (rpos.y = 0, gpos.y = input.origin_in_voxels.y; rpos.y < bs.y; ++rpos.y, gpos.y += stride) {
out_buffer.set_voxel_f(generate_single(gpos), rpos.x, rpos.y, rpos.z, channel);
}
}
}
out_buffer.compress_uniform_channels();
}
template <typename T>
inline void write_static(std::vector<uint8_t> &mem, uint32_t p, const T &v) {
CRASH_COND(p + sizeof(T) >= mem.size());
*(T *)(&mem[p]) = v;
}
template <typename T>
inline void append(std::vector<uint8_t> &mem, const T &v) {
size_t p = mem.size();
mem.resize(p + sizeof(T));
*(T *)(&mem[p]) = v;
}
template <typename T>
inline void write_op(std::vector<uint8_t> &mem, uint8_t opid, T data) {
uint32_t p = mem.size();
mem.resize(p + 1 + sizeof(T));
mem[p++] = opid;
write_static(mem, p, data);
}
template <typename T>
inline const T &read(const std::vector<uint8_t> &mem, uint32_t &p) {
CRASH_COND(p + sizeof(T) >= mem.size());
const T *v = (const T *)&mem[p];
p += sizeof(T);
return *v;
}
inline float get_pixel_repeat(Image &im, int x, int y) {
return im.get_pixel(wrap(x, im.get_width()), wrap(y, im.get_height())).r;
}
inline float squared(float x) {
return x * x;
}
void VoxelGeneratorGraph::compile() {
std::vector<uint32_t> order;
_graph.evaluate(order);
_program.clear();
// Main inputs X, Y, Z
_memory.resize(3);
std::vector<uint8_t> &program = _program;
const NodeTypeDB &type_db = *NodeTypeDB::get_singleton();
HashMap<ProgramGraph::PortLocation, uint16_t, ProgramGraph::PortLocationHasher> output_port_addresses;
bool has_output = false;
for (size_t i = 0; i < order.size(); ++i) {
const uint32_t node_id = order[i];
const ProgramGraph::Node *pg_node = _graph.get_node(node_id);
const Node *node = _nodes[node_id];
const NodeTypeDB::NodeType &type = type_db.types[node->type];
CRASH_COND(node == nullptr);
CRASH_COND(pg_node->inputs.size() != type.inputs.size());
CRASH_COND(pg_node->outputs.size() != type.outputs.size());
switch (node->type) {
case NODE_CONSTANT: {
CRASH_COND(type.outputs.size() != 1);
CRASH_COND(type.params.size() != 1);
uint16_t a = _memory.size();
_memory.push_back(node->params[0].operator float());
output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = a;
} break;
case NODE_INPUT_X:
output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 0;
break;
case NODE_INPUT_Y:
output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 1;
break;
case NODE_INPUT_Z:
output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 2;
break;
case NODE_OUTPUT_SDF:
// TODO Multiple outputs may be supported if we get branching
CRASH_COND(has_output);
has_output = true;
break;
default: {
append(program, static_cast<uint8_t>(node->type));
// Add inputs
for (size_t j = 0; j < type.inputs.size(); ++j) {
uint16_t a;
if (pg_node->inputs[j].connections.size() == 0) {
// No input, default it
// TODO Take param value if specified
a = _memory.size();
_memory.push_back(0);
} else {
ProgramGraph::PortLocation src_port = pg_node->inputs[j].connections[0];
const uint16_t *aptr = output_port_addresses.getptr(src_port);
// Previous node ports must have been registered
CRASH_COND(aptr == nullptr);
a = *aptr;
}
append(program, a);
}
// Add outputs
for (size_t j = 0; j < type.outputs.size(); ++j) {
const uint16_t a = _memory.size();
_memory.push_back(0);
// This will be used by next nodes
const ProgramGraph::PortLocation op{ node_id, static_cast<uint32_t>(j) };
output_port_addresses[op] = a;
append(program, a);
}
// Add special params
switch (node->type) {
case NODE_CURVE: {
Ref<Curve> curve = node->params[0];
CRASH_COND(curve.is_null());
append(program, *curve);
} break;
case NODE_IMAGE_2D: {
Ref<Image> im = node->params[0];
CRASH_COND(im.is_null());
append(program, *im);
} break;
case NODE_NOISE_2D:
case NODE_NOISE_3D: {
Ref<OpenSimplexNoise> noise = node->params[0];
CRASH_COND(noise.is_null());
append(program, *noise);
} break;
// TODO Worth it?
case NODE_CLAMP:
append(program, node->params[0].operator float());
append(program, node->params[1].operator float());
break;
case NODE_REMAP: {
float min0 = node->params[0].operator float();
float max0 = node->params[1].operator float();
float min1 = node->params[2].operator float();
float max1 = node->params[3].operator float();
append(program, -min0);
append(program, Math::is_equal_approx(max0, min0) ? 99999.f : 1.f / (max0 - min0));
append(program, min1);
append(program, max1 - min1);
} break;
}
} break;
}
}
if (_memory.size() < 4) {
// In case there is nothing
_memory.resize(4, 0);
}
CRASH_COND(!has_output);
}
// The order of fields in the following structs matters.
// Inputs go first, then outputs, then params (if applicable at runtime).
struct PNodeBinop {
uint16_t a_i0;
uint16_t a_i1;
uint16_t a_out;
};
struct PNodeMonoFunc {
uint16_t a_in;
uint16_t a_out;
};
struct PNodeDistance2D {
uint16_t a_x0;
uint16_t a_y0;
uint16_t a_x1;
uint16_t a_y1;
uint16_t a_out;
};
struct PNodeDistance3D {
uint16_t a_x0;
uint16_t a_y0;
uint16_t a_z0;
uint16_t a_x1;
uint16_t a_y1;
uint16_t a_z1;
uint16_t a_out;
};
struct PNodeClamp {
uint16_t a_x;
uint16_t a_out;
float p_min;
float p_max;
};
struct PNodeMix {
uint16_t a_i0;
uint16_t a_i1;
uint16_t a_ratio;
uint16_t a_out;
};
struct PNodeRemap {
uint16_t a_x;
uint16_t a_out;
float p_c0;
float p_m0;
float p_c1;
float p_m1;
};
struct PNodeCurve {
uint16_t a_in;
uint16_t a_out;
Curve *p_curve;
};
struct PNodeNoise2D {
uint16_t a_x;
uint16_t a_y;
uint16_t a_out;
OpenSimplexNoise *p_noise;
};
struct PNodeNoise3D {
uint16_t a_x;
uint16_t a_y;
uint16_t a_z;
uint16_t a_out;
OpenSimplexNoise *p_noise;
};
struct PNodeImage2D {
uint16_t a_x;
uint16_t a_y;
uint16_t a_out;
Image *p_image;
};
float VoxelGeneratorGraph::generate_single(const Vector3i &position) {
// This part must be optimized for speed
std::vector<float> &memory = _memory;
memory[0] = position.x;
memory[1] = position.y;
memory[2] = position.z;
uint32_t pc = 0;
while (pc < _program.size()) {
const uint8_t opid = _program[pc++];
switch (opid) {
case NODE_CONSTANT:
case NODE_INPUT_X:
case NODE_INPUT_Y:
case NODE_INPUT_Z:
case NODE_OUTPUT_SDF:
// Not part of the runtime
CRASH_NOW();
break;
case NODE_ADD: {
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
memory[n.a_out] = memory[n.a_i0] + memory[n.a_i1];
} break;
case NODE_SUBTRACT: {
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
memory[n.a_out] = memory[n.a_i0] - memory[n.a_i1];
} break;
case NODE_MULTIPLY: {
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
memory[n.a_out] = memory[n.a_i0] * memory[n.a_i1];
} break;
case NODE_SINE: {
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
memory[n.a_out] = Math::sin(Math_PI * memory[n.a_in]);
} break;
case NODE_FLOOR: {
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
memory[n.a_out] = Math::floor(memory[n.a_in]);
} break;
case NODE_ABS: {
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
memory[n.a_out] = Math::abs(memory[n.a_in]);
} break;
case NODE_SQRT: {
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
memory[n.a_out] = Math::sqrt(memory[n.a_in]);
} break;
case NODE_DISTANCE_2D: {
const PNodeDistance2D &n = read<PNodeDistance2D>(_program, pc);
memory[n.a_out] = Math::sqrt(squared(memory[n.a_x1] - memory[n.a_x0]) +
squared(memory[n.a_y1] - memory[n.a_y0]));
} break;
case NODE_DISTANCE_3D: {
const PNodeDistance3D &n = read<PNodeDistance3D>(_program, pc);
memory[n.a_out] = Math::sqrt(squared(memory[n.a_x1] - memory[n.a_x0]) +
squared(memory[n.a_y1] - memory[n.a_y0]) +
squared(memory[n.a_z1] - memory[n.a_z0]));
} break;
case NODE_MIX: {
const PNodeMix &n = read<PNodeMix>(_program, pc);
memory[n.a_out] = Math::lerp(memory[n.a_i0], memory[n.a_i1], memory[n.a_ratio]);
} break;
case NODE_CLAMP: {
const PNodeClamp &n = read<PNodeClamp>(_program, pc);
memory[n.a_out] = clamp(memory[n.a_x], memory[n.p_min], memory[n.p_max]);
} break;
case NODE_REMAP: {
const PNodeRemap &n = read<PNodeRemap>(_program, pc);
memory[n.a_out] = ((memory[n.a_x] - n.p_c0) * n.p_m0) * n.p_m1 + n.p_c1;
} break;
case NODE_CURVE: {
const PNodeCurve &n = read<PNodeCurve>(_program, pc);
memory[n.a_out] = n.p_curve->interpolate_baked(memory[n.a_in]);
} break;
case NODE_NOISE_2D: {
const PNodeNoise2D &n = read<PNodeNoise2D>(_program, pc);
memory[n.a_out] = n.p_noise->get_noise_2d(memory[n.a_x], memory[n.a_y]);
} break;
case NODE_NOISE_3D: {
const PNodeNoise3D &n = read<PNodeNoise3D>(_program, pc);
memory[n.a_out] = n.p_noise->get_noise_3d(memory[n.a_x], memory[n.a_y], memory[n.a_z]);
} break;
case NODE_IMAGE_2D: {
const PNodeImage2D &n = read<PNodeImage2D>(_program, pc);
// TODO Not great, but in Godot 4.0 we won't need to lock anymore. Otherwise, need to do it in a pre-run and post-run
n.p_image->lock();
memory[n.a_out] = get_pixel_repeat(*n.p_image, memory[n.a_x], memory[n.a_y]);
n.p_image->unlock();
} break;
default:
CRASH_NOW();
break;
}
}
return memory.back() * _iso_scale;
}
void VoxelGeneratorGraph::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &VoxelGeneratorGraph::clear);
}

View File

@ -0,0 +1,104 @@
#ifndef VOXEL_GENERATOR_GRAPH_H
#define VOXEL_GENERATOR_GRAPH_H
#include "../voxel_generator.h"
#include "program_graph.h"
#include <modules/opensimplex/open_simplex_noise.h>
class VoxelGeneratorGraph : public VoxelGenerator {
GDCLASS(VoxelGeneratorGraph, VoxelGenerator)
public:
enum NodeTypeID {
NODE_CONSTANT,
NODE_INPUT_X,
NODE_INPUT_Y,
NODE_INPUT_Z,
NODE_OUTPUT_SDF,
NODE_ADD,
NODE_SUBTRACT,
NODE_MULTIPLY,
NODE_SINE,
NODE_FLOOR,
NODE_ABS,
NODE_SQRT,
NODE_DISTANCE_2D,
NODE_DISTANCE_3D,
NODE_CLAMP,
NODE_MIX,
NODE_REMAP,
NODE_CURVE,
NODE_NOISE_2D,
NODE_NOISE_3D,
NODE_IMAGE_2D,
NODE_TYPE_COUNT
};
struct NodeTypeDB {
struct Port {
String name;
bool fixed_address;
Port(String p_name, bool p_fixed_address = false) :
name(p_name),
fixed_address(p_fixed_address) {}
};
struct Param {
String name;
float default_value;
Param(String p_name, float p_default_value = 0) :
name(p_name),
default_value(p_default_value) {}
};
struct NodeType {
std::vector<Port> inputs;
std::vector<Port> outputs;
std::vector<Param> params;
};
NodeTypeDB();
static NodeTypeDB *get_singleton();
static void create_singleton();
static void destroy_singleton();
FixedArray<NodeType, NODE_TYPE_COUNT> types;
};
VoxelGeneratorGraph();
~VoxelGeneratorGraph();
void clear();
uint32_t create_node(NodeTypeID type_id);
void remove_node(uint32_t node_id);
void node_connect(ProgramGraph::PortLocation src, ProgramGraph::PortLocation dst);
void node_set_param(uint32_t node_id, uint32_t param_index, Variant value);
void generate_block(VoxelBlockRequest &input) override;
float generate_single(const Vector3i &position);
private:
void compile();
static void _bind_methods();
struct Node {
NodeTypeID type;
std::vector<Variant> params;
Vector2 gui_position;
};
ProgramGraph _graph;
HashMap<uint32_t, Node *> _nodes;
std::vector<uint8_t> _program;
std::vector<float> _memory;
VoxelBuffer::ChannelId _channel = VoxelBuffer::CHANNEL_SDF;
float _iso_scale = 0.1;
};
#endif // VOXEL_GENERATOR_GRAPH_H

View File

@ -1,5 +1,6 @@
#include "register_types.h"
#include "edition/voxel_tool.h"
#include "generators/graph/voxel_generator_graph.h"
#include "generators/voxel_generator_flat.h"
#include "generators/voxel_generator_heightmap.h"
#include "generators/voxel_generator_image.h"
@ -49,6 +50,7 @@ void register_voxel_types() {
ClassDB::register_class<VoxelGeneratorImage>();
ClassDB::register_class<VoxelGeneratorNoise2D>();
ClassDB::register_class<VoxelGeneratorNoise>();
ClassDB::register_class<VoxelGeneratorGraph>();
// Helpers
ClassDB::register_class<VoxelBoxMover>();
@ -63,6 +65,7 @@ void register_voxel_types() {
VoxelMemoryPool::create_singleton();
VoxelStringNames::create_singleton();
VoxelGeneratorGraph::NodeTypeDB::create_singleton();
#ifdef TOOLS_ENABLED
VoxelDebug::create_debug_box_mesh();
@ -79,6 +82,8 @@ void unregister_voxel_types() {
VoxelStringNames::destroy_singleton();
VoxelGeneratorGraph::NodeTypeDB::destroy_singleton();
#ifdef TOOLS_ENABLED
VoxelDebug::free_debug_box_mesh();
#endif