A bit of reorganization:
- Merged VoxelGeneratorGraph::Node into ProgramGraph::Node - Separated compilation and execution to its own class
This commit is contained in:
parent
dcc53d9351
commit
a3b64c3983
@ -1,5 +1,6 @@
|
||||
#include "program_graph.h"
|
||||
#include <core/os/file_access.h>
|
||||
#include <core/resource.h>
|
||||
#include <core/variant.h>
|
||||
#include <unordered_set>
|
||||
|
||||
@ -36,9 +37,10 @@ uint32_t ProgramGraph::Node::find_output_connection(uint32_t output_port_index,
|
||||
return ProgramGraph::NULL_INDEX;
|
||||
}
|
||||
|
||||
ProgramGraph::Node *ProgramGraph::create_node() {
|
||||
ProgramGraph::Node *ProgramGraph::create_node(uint32_t type_id) {
|
||||
Node *node = memnew(Node);
|
||||
node->id = _next_node_id++;
|
||||
node->type_id = type_id;
|
||||
_nodes[node->id] = node;
|
||||
return node;
|
||||
}
|
||||
@ -142,6 +144,14 @@ ProgramGraph::Node *ProgramGraph::get_node(uint32_t id) const {
|
||||
return node;
|
||||
}
|
||||
|
||||
ProgramGraph::Node *ProgramGraph::try_get_node(uint32_t id) const {
|
||||
auto it = _nodes.find(id);
|
||||
if (it == _nodes.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
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;
|
||||
@ -287,16 +297,30 @@ void ProgramGraph::debug_print_dot_file(String file_path) const {
|
||||
memdelete(f);
|
||||
}
|
||||
|
||||
void ProgramGraph::copy_from(const ProgramGraph &other) {
|
||||
void ProgramGraph::copy_from(const ProgramGraph &other, bool copy_subresources) {
|
||||
clear();
|
||||
|
||||
_next_node_id = other._next_node_id;
|
||||
_nodes.reserve(other._nodes.size());
|
||||
|
||||
for (auto it = other._nodes.begin(); it != other._nodes.end(); ++it) {
|
||||
const Node *other_node = it->second;
|
||||
|
||||
Node *node = memnew(Node);
|
||||
node->id = other_node->id;
|
||||
node->inputs = other_node->inputs;
|
||||
node->outputs = other_node->outputs;
|
||||
*node = *other_node;
|
||||
|
||||
if (copy_subresources) {
|
||||
for (size_t i = 0; i < node->params.size(); ++i) {
|
||||
Object *obj = node->params[i];
|
||||
if (obj != nullptr) {
|
||||
Resource *res = Object::cast_to<Resource>(obj);
|
||||
if (res != nullptr) {
|
||||
node->params[i] = res->duplicate(copy_subresources);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_nodes.insert(std::make_pair(node->id, node));
|
||||
}
|
||||
}
|
||||
@ -317,3 +341,16 @@ void ProgramGraph::get_connections(std::vector<ProgramGraph::Connection> &connec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PoolVector<int> ProgramGraph::get_node_ids() const {
|
||||
PoolIntArray ids;
|
||||
ids.resize(_nodes.size());
|
||||
{
|
||||
PoolIntArray::Write w = ids.write();
|
||||
int i = 0;
|
||||
for (auto it = _nodes.begin(); it != _nodes.end(); ++it) {
|
||||
w[i++] = it->first;
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
@ -2,9 +2,12 @@
|
||||
#define PROGRAM_GRAPH_H
|
||||
|
||||
#include <core/hashfuncs.h>
|
||||
#include <core/math/vector2.h>
|
||||
#include <core/pool_vector.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// Generic graph representing a program
|
||||
class ProgramGraph {
|
||||
public:
|
||||
static const uint32_t NULL_ID = 0;
|
||||
@ -33,15 +36,19 @@ public:
|
||||
|
||||
struct Node {
|
||||
uint32_t id;
|
||||
uint32_t type_id;
|
||||
Vector2 gui_position;
|
||||
std::vector<Port> inputs;
|
||||
std::vector<Port> outputs;
|
||||
std::vector<Variant> params;
|
||||
|
||||
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 *create_node(uint32_t type_id);
|
||||
Node *get_node(uint32_t id) const;
|
||||
Node *try_get_node(uint32_t id) const;
|
||||
void remove_node(uint32_t id);
|
||||
void clear();
|
||||
|
||||
@ -55,8 +62,9 @@ public:
|
||||
void find_depth_first(uint32_t start_node_id, std::vector<uint32_t> &order) const;
|
||||
void find_terminal_nodes(std::vector<uint32_t> &node_ids) const;
|
||||
|
||||
void copy_from(const ProgramGraph &other);
|
||||
void copy_from(const ProgramGraph &other, bool copy_subresources);
|
||||
void get_connections(std::vector<ProgramGraph::Connection> &connections) const;
|
||||
PoolVector<int> get_node_ids() const;
|
||||
|
||||
void debug_print_dot_file(String file_path) const;
|
||||
|
||||
|
@ -1,13 +1,8 @@
|
||||
#include "voxel_generator_graph.h"
|
||||
#include "../../util/profiling_clock.h"
|
||||
#include "../../voxel_string_names.h"
|
||||
#include "range_utility.h"
|
||||
#include "voxel_graph_node_db.h"
|
||||
|
||||
//#ifdef DEBUG_ENABLED
|
||||
//#define VOXEL_DEBUG_GRAPH_PROG_SENTINEL uint16_t(12345) // 48, 57 (base 10)
|
||||
//#endif
|
||||
|
||||
VoxelGeneratorGraph::VoxelGeneratorGraph() {
|
||||
clear();
|
||||
clear_bounds();
|
||||
@ -24,46 +19,27 @@ VoxelGeneratorGraph::~VoxelGeneratorGraph() {
|
||||
}
|
||||
|
||||
void VoxelGeneratorGraph::clear() {
|
||||
const uint32_t *key = nullptr;
|
||||
while ((key = _nodes.next(key))) {
|
||||
Node *node = _nodes.get(*key);
|
||||
CRASH_COND(node == nullptr);
|
||||
memdelete(node);
|
||||
}
|
||||
_nodes.clear();
|
||||
_graph.clear();
|
||||
|
||||
_program.clear();
|
||||
_memory.resize(8, 0);
|
||||
_runtime.clear();
|
||||
}
|
||||
|
||||
uint32_t VoxelGeneratorGraph::create_node(NodeTypeID type_id) {
|
||||
const VoxelGraphNodeDB::NodeType &type = VoxelGraphNodeDB::get_singleton()->get_type(type_id);
|
||||
|
||||
ProgramGraph::Node *pg_node = _graph.create_node();
|
||||
pg_node->inputs.resize(type.inputs.size());
|
||||
pg_node->outputs.resize(type.outputs.size());
|
||||
ProgramGraph::Node *node = _graph.create_node(type_id);
|
||||
node->inputs.resize(type.inputs.size());
|
||||
node->outputs.resize(type.outputs.size());
|
||||
|
||||
Node *node = memnew(Node);
|
||||
node->type = type_id;
|
||||
node->params.resize(type.params.size());
|
||||
for (size_t i = 0; i < type.params.size(); ++i) {
|
||||
node->params[i] = type.params[i].default_value;
|
||||
}
|
||||
_nodes[pg_node->id] = node;
|
||||
|
||||
return pg_node->id;
|
||||
return 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);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelGeneratorGraph::can_connect(uint32_t src_node_id, uint32_t src_port_index, uint32_t dst_node_id, uint32_t dst_port_index) const {
|
||||
@ -89,54 +65,40 @@ void VoxelGeneratorGraph::get_connections(std::vector<ProgramGraph::Connection>
|
||||
}
|
||||
|
||||
void VoxelGeneratorGraph::set_node_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;
|
||||
ProgramGraph::Node *node = _graph.try_get_node(node_id);
|
||||
ERR_FAIL_COND(node == nullptr);
|
||||
ERR_FAIL_INDEX(param_index, node->params.size());
|
||||
node->params[param_index] = value;
|
||||
}
|
||||
|
||||
Variant VoxelGeneratorGraph::get_node_param(uint32_t node_id, uint32_t param_index) const {
|
||||
const Node *const *pptr = _nodes.getptr(node_id);
|
||||
ERR_FAIL_COND_V(pptr == nullptr, Variant());
|
||||
const Node *node = *pptr;
|
||||
const ProgramGraph::Node *node = _graph.try_get_node(node_id);
|
||||
ERR_FAIL_COND_V(node == nullptr, Variant());
|
||||
ERR_FAIL_INDEX_V(param_index, node->params.size(), Variant());
|
||||
return node->params[param_index];
|
||||
}
|
||||
|
||||
Vector2 VoxelGeneratorGraph::get_node_gui_position(uint32_t node_id) const {
|
||||
const Node *const *pptr = _nodes.getptr(node_id);
|
||||
ERR_FAIL_COND_V(pptr == nullptr, Vector2());
|
||||
const Node *node = *pptr;
|
||||
const ProgramGraph::Node *node = _graph.try_get_node(node_id);
|
||||
ERR_FAIL_COND_V(node == nullptr, Vector2());
|
||||
return node->gui_position;
|
||||
}
|
||||
|
||||
void VoxelGeneratorGraph::set_node_gui_position(uint32_t node_id, Vector2 pos) {
|
||||
Node **node_pptr = _nodes.getptr(node_id);
|
||||
ERR_FAIL_COND(node_pptr == nullptr);
|
||||
Node *node = *node_pptr;
|
||||
ProgramGraph::Node *node = _graph.try_get_node(node_id);
|
||||
ERR_FAIL_COND(node == nullptr);
|
||||
node->gui_position = pos;
|
||||
}
|
||||
|
||||
VoxelGeneratorGraph::NodeTypeID VoxelGeneratorGraph::get_node_type_id(uint32_t node_id) {
|
||||
Node **node_pptr = _nodes.getptr(node_id);
|
||||
ERR_FAIL_COND_V(node_pptr == nullptr, NODE_TYPE_COUNT);
|
||||
Node *node = *node_pptr;
|
||||
return node->type;
|
||||
const ProgramGraph::Node *node = _graph.try_get_node(node_id);
|
||||
ERR_FAIL_COND_V(node == nullptr, NODE_TYPE_COUNT);
|
||||
CRASH_COND(node->type_id >= NODE_TYPE_COUNT);
|
||||
return (NodeTypeID)node->type_id;
|
||||
}
|
||||
|
||||
PoolIntArray VoxelGeneratorGraph::get_node_ids() const {
|
||||
PoolIntArray ids;
|
||||
ids.resize(_nodes.size());
|
||||
{
|
||||
PoolIntArray::Write w = ids.write();
|
||||
int i = 0;
|
||||
const uint32_t *key = nullptr;
|
||||
while (key = _nodes.next(key)) {
|
||||
w[i++] = *key;
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
return _graph.get_node_ids();
|
||||
}
|
||||
|
||||
int VoxelGeneratorGraph::get_used_channels_mask() const {
|
||||
@ -233,298 +195,11 @@ void VoxelGeneratorGraph::generate_block(VoxelBlockRequest &input) {
|
||||
out_buffer.compress_uniform_channels();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void write_static(std::vector<uint8_t> &mem, uint32_t p, const T &v) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(p + sizeof(T) >= mem.size());
|
||||
#endif
|
||||
*(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 const T &read(const std::vector<uint8_t> &mem, uint32_t &p) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(p + sizeof(T) > mem.size());
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
void VoxelGeneratorGraph::compile() {
|
||||
std::vector<uint32_t> order;
|
||||
std::vector<uint32_t> terminal_nodes;
|
||||
|
||||
_graph.find_terminal_nodes(terminal_nodes);
|
||||
// For now only 1 end is supported
|
||||
ERR_FAIL_COND(terminal_nodes.size() != 1);
|
||||
|
||||
_graph.find_dependencies(terminal_nodes.back(), order);
|
||||
|
||||
_program.clear();
|
||||
|
||||
// Main inputs X, Y, Z
|
||||
_memory.resize(3);
|
||||
|
||||
std::vector<uint8_t> &program = _program;
|
||||
const VoxelGraphNodeDB &type_db = *VoxelGraphNodeDB::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 VoxelGraphNodeDB::NodeType &type = type_db.get_type(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: {
|
||||
// Add actual operation
|
||||
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());
|
||||
uint8_t is_monotonic_increasing;
|
||||
Interval range = get_curve_range(**curve, is_monotonic_increasing);
|
||||
append(program, is_monotonic_increasing);
|
||||
append(program, range.min);
|
||||
append(program, range.max);
|
||||
append(program, *curve);
|
||||
} break;
|
||||
|
||||
case NODE_IMAGE_2D: {
|
||||
Ref<Image> im = node->params[0];
|
||||
CRASH_COND(im.is_null());
|
||||
Interval range = get_heightmap_range(**im);
|
||||
append(program, range.min);
|
||||
append(program, range.max);
|
||||
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;
|
||||
|
||||
} // switch special params
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// Append a special value after each operation
|
||||
append(program, VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
|
||||
} break; // default
|
||||
|
||||
} // switch type
|
||||
}
|
||||
|
||||
if (_memory.size() < 4) {
|
||||
// In case there is nothing
|
||||
_memory.resize(4, 0);
|
||||
}
|
||||
|
||||
// Reserve space for range analysis
|
||||
_memory.resize(_memory.size() * 2);
|
||||
// Make it a copy to keep eventual constants at consistent adresses
|
||||
const size_t half_size = _memory.size() / 2;
|
||||
for (size_t i = 0, j = half_size; i < half_size; ++i, ++j) {
|
||||
_memory[j] = _memory[i];
|
||||
}
|
||||
|
||||
print_line(String("Compiled voxel graph. Program size: {0}b, memory size: {1}b")
|
||||
.format(varray(_program.size() * sizeof(float), _memory.size() * sizeof(float))));
|
||||
|
||||
CRASH_COND(!has_output);
|
||||
_runtime.compile(_graph);
|
||||
}
|
||||
|
||||
// The order of fields in the following structs matters.
|
||||
// Inputs go first, then outputs, then params (if applicable at runtime).
|
||||
// TODO Think about alignment
|
||||
|
||||
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;
|
||||
uint8_t is_monotonic_increasing;
|
||||
float min_value;
|
||||
float max_value;
|
||||
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;
|
||||
float min_value;
|
||||
float max_value;
|
||||
Image *p_image;
|
||||
};
|
||||
|
||||
float VoxelGeneratorGraph::generate_single(const Vector3i &position) {
|
||||
// This part must be optimized for speed
|
||||
|
||||
switch (_bounds.type) {
|
||||
case BOUNDS_NONE:
|
||||
@ -557,324 +232,11 @@ float VoxelGeneratorGraph::generate_single(const Vector3i &position) {
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(_memory.size() == 0);
|
||||
#endif
|
||||
|
||||
ArraySlice<float> memory(_memory, 0, _memory.size() / 2);
|
||||
memory[0] = position.x;
|
||||
memory[1] = position.y;
|
||||
memory[2] = position.z;
|
||||
|
||||
// STL is unreadable on debug builds of Godot, because _DEBUG isn't defined
|
||||
//#ifdef DEBUG_ENABLED
|
||||
// const size_t memory_size = memory.size();
|
||||
// const size_t program_size = _program.size();
|
||||
// const float *memory_raw = memory.data();
|
||||
// const uint8_t *program_raw = (const uint8_t *)_program.data();
|
||||
//#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// If this fails, the program is ill-formed
|
||||
CRASH_COND(read<uint16_t>(_program, pc) != VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
return memory[memory.size() - 1] * _iso_scale;
|
||||
return _runtime.generate_single(position) * _iso_scale;
|
||||
}
|
||||
|
||||
Interval VoxelGeneratorGraph::analyze_range(Vector3i min_pos, Vector3i max_pos) {
|
||||
|
||||
ArraySlice<float> min_memory(_memory, 0, _memory.size() / 2);
|
||||
ArraySlice<float> max_memory(_memory, _memory.size() / 2, _memory.size());
|
||||
min_memory[0] = min_pos.x;
|
||||
min_memory[1] = min_pos.y;
|
||||
min_memory[2] = min_pos.z;
|
||||
max_memory[0] = max_pos.x;
|
||||
max_memory[1] = max_pos.y;
|
||||
max_memory[2] = max_pos.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);
|
||||
min_memory[n.a_out] = min_memory[n.a_i0] + min_memory[n.a_i1];
|
||||
max_memory[n.a_out] = max_memory[n.a_i0] + max_memory[n.a_i1];
|
||||
} break;
|
||||
|
||||
case NODE_SUBTRACT: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
min_memory[n.a_out] = min_memory[n.a_i0] - max_memory[n.a_i1];
|
||||
max_memory[n.a_out] = max_memory[n.a_i0] - min_memory[n.a_i1];
|
||||
} break;
|
||||
|
||||
case NODE_MULTIPLY: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
Interval r = Interval(min_memory[n.a_i0], max_memory[n.a_i0]) *
|
||||
Interval(min_memory[n.a_i1], max_memory[n.a_i1]);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_SINE: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
Interval r = sin(Interval(min_memory[n.a_in], max_memory[n.a_in]) * Math_PI);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_FLOOR: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
// Floor is monotonic so I guess we can just do that
|
||||
min_memory[n.a_out] = Math::floor(min_memory[n.a_in]);
|
||||
max_memory[n.a_out] = Math::floor(max_memory[n.a_in]); // ceil?
|
||||
} break;
|
||||
|
||||
case NODE_ABS: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
Interval r = abs(Interval(min_memory[n.a_in], max_memory[n.a_in]));
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_SQRT: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
Interval r = sqrt(Interval(min_memory[n.a_in], max_memory[n.a_in]));
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_DISTANCE_2D: {
|
||||
const PNodeDistance2D &n = read<PNodeDistance2D>(_program, pc);
|
||||
Interval x0(min_memory[n.a_x0], max_memory[n.a_x0]);
|
||||
Interval y0(min_memory[n.a_y0], max_memory[n.a_y0]);
|
||||
Interval x1(min_memory[n.a_x1], max_memory[n.a_x1]);
|
||||
Interval y1(min_memory[n.a_y1], max_memory[n.a_y1]);
|
||||
Interval dx = x1 - x0;
|
||||
Interval dy = y1 - y0;
|
||||
Interval r = sqrt(dx * dx + dy * dy);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_DISTANCE_3D: {
|
||||
const PNodeDistance3D &n = read<PNodeDistance3D>(_program, pc);
|
||||
Interval x0(min_memory[n.a_x0], max_memory[n.a_x0]);
|
||||
Interval y0(min_memory[n.a_y0], max_memory[n.a_y0]);
|
||||
Interval z0(min_memory[n.a_z0], max_memory[n.a_z0]);
|
||||
Interval x1(min_memory[n.a_x1], max_memory[n.a_x1]);
|
||||
Interval y1(min_memory[n.a_y1], max_memory[n.a_y1]);
|
||||
Interval z1(min_memory[n.a_z1], max_memory[n.a_z1]);
|
||||
Interval dx = x1 - x0;
|
||||
Interval dy = y1 - y0;
|
||||
Interval dz = z1 - z0;
|
||||
Interval r = sqrt(dx * dx + dy * dy + dz * dz);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_MIX: {
|
||||
const PNodeMix &n = read<PNodeMix>(_program, pc);
|
||||
Interval a(min_memory[n.a_i0], max_memory[n.a_i0]);
|
||||
Interval b(min_memory[n.a_i1], max_memory[n.a_i1]);
|
||||
Interval t(min_memory[n.a_ratio], max_memory[n.a_ratio]);
|
||||
Interval r = lerp(a, b, t);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_CLAMP: {
|
||||
const PNodeClamp &n = read<PNodeClamp>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
// TODO We may want to have wirable min and max later
|
||||
Interval cmin = Interval::from_single_value(n.p_min);
|
||||
Interval cmax = Interval::from_single_value(n.p_max);
|
||||
Interval r = clamp(x, cmin, cmax);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_REMAP: {
|
||||
const PNodeRemap &n = read<PNodeRemap>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
Interval r = ((x - n.p_c0) * n.p_m0) * n.p_m1 + n.p_c1;
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_CURVE: {
|
||||
const PNodeCurve &n = read<PNodeCurve>(_program, pc);
|
||||
if (min_memory[n.a_in] == max_memory[n.a_in]) {
|
||||
float v = n.p_curve->interpolate_baked(min_memory[n.a_in]);
|
||||
min_memory[n.a_out] = v;
|
||||
max_memory[n.a_out] = v;
|
||||
} else if (n.is_monotonic_increasing) {
|
||||
min_memory[n.a_out] = n.p_curve->interpolate_baked(min_memory[n.a_in]);
|
||||
max_memory[n.a_out] = n.p_curve->interpolate_baked(max_memory[n.a_in]);
|
||||
} else {
|
||||
// TODO Segment the curve?
|
||||
min_memory[n.a_out] = n.min_value;
|
||||
max_memory[n.a_out] = n.max_value;
|
||||
}
|
||||
} break;
|
||||
|
||||
case NODE_NOISE_2D: {
|
||||
const PNodeNoise2D &n = read<PNodeNoise2D>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
Interval y(min_memory[n.a_y], max_memory[n.a_y]);
|
||||
Interval r = get_osn_range_2d(n.p_noise, x, y);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_NOISE_3D: {
|
||||
const PNodeNoise3D &n = read<PNodeNoise3D>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
Interval y(min_memory[n.a_y], max_memory[n.a_y]);
|
||||
Interval z(min_memory[n.a_z], max_memory[n.a_z]);
|
||||
Interval r = get_osn_range_3d(n.p_noise, x, y, z);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case NODE_IMAGE_2D: {
|
||||
const PNodeImage2D &n = read<PNodeImage2D>(_program, pc);
|
||||
// TODO Segment image?
|
||||
min_memory[n.a_out] = n.min_value;
|
||||
max_memory[n.a_out] = n.max_value;
|
||||
} break;
|
||||
|
||||
default:
|
||||
CRASH_NOW();
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// If this fails, the program is ill-formed
|
||||
CRASH_COND(read<uint16_t>(_program, pc) != VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
return Interval(min_memory[min_memory.size() - 1], max_memory[max_memory.size() - 1]) * _iso_scale;
|
||||
return _runtime.analyze_range(min_pos, max_pos) * _iso_scale;
|
||||
}
|
||||
|
||||
void VoxelGeneratorGraph::clear_bounds() {
|
||||
@ -910,33 +272,9 @@ Ref<Resource> VoxelGeneratorGraph::duplicate(bool p_subresources) const {
|
||||
d->_channel = _channel;
|
||||
d->_iso_scale = _iso_scale;
|
||||
d->_bounds = _bounds;
|
||||
d->_graph.copy_from(_graph);
|
||||
d->_graph.copy_from(_graph, p_subresources);
|
||||
// Program not copied, as it may contain pointers to the resources we are duplicating
|
||||
|
||||
const uint32_t *key = nullptr;
|
||||
while ((key = _nodes.next(key))) {
|
||||
Node *node = _nodes.get(*key);
|
||||
Node *node_copy = memnew(Node);
|
||||
|
||||
node_copy->type = node->type;
|
||||
node_copy->gui_position = node->gui_position;
|
||||
node_copy->params = node->params;
|
||||
|
||||
if (p_subresources) {
|
||||
for (auto it = node_copy->params.begin(); it != node_copy->params.end(); ++it) {
|
||||
Object *obj = *it;
|
||||
if (obj != nullptr) {
|
||||
Resource *res = Object::cast_to<Resource>(obj);
|
||||
if (res != nullptr) {
|
||||
*it = res->duplicate(p_subresources);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d->_nodes.set(*key, node_copy);
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "../../math/interval.h"
|
||||
#include "../voxel_generator.h"
|
||||
#include "program_graph.h"
|
||||
#include "voxel_graph_runtime.h"
|
||||
|
||||
class VoxelGeneratorGraph : public VoxelGenerator {
|
||||
GDCLASS(VoxelGeneratorGraph, VoxelGenerator)
|
||||
@ -94,12 +95,6 @@ private:
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
struct Node {
|
||||
NodeTypeID type;
|
||||
std::vector<Variant> params;
|
||||
Vector2 gui_position;
|
||||
};
|
||||
|
||||
struct Bounds {
|
||||
BoundsType type = BOUNDS_NONE;
|
||||
Vector3i min;
|
||||
@ -112,10 +107,7 @@ private:
|
||||
};
|
||||
|
||||
ProgramGraph _graph;
|
||||
HashMap<uint32_t, Node *> _nodes;
|
||||
|
||||
std::vector<uint8_t> _program;
|
||||
std::vector<float> _memory;
|
||||
VoxelGraphRuntime _runtime;
|
||||
VoxelBuffer::ChannelId _channel = VoxelBuffer::CHANNEL_SDF;
|
||||
float _iso_scale = 0.1;
|
||||
Bounds _bounds;
|
||||
|
@ -174,7 +174,7 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary VoxelGraphNodeDB::get_type_info_dict(int id) const {
|
||||
Dictionary VoxelGraphNodeDB::get_type_info_dict(uint32_t id) const {
|
||||
const NodeType &type = _types[id];
|
||||
|
||||
Dictionary type_dict;
|
||||
|
@ -44,8 +44,8 @@ public:
|
||||
static void destroy_singleton();
|
||||
|
||||
int get_type_count() const { return _types.size(); }
|
||||
const NodeType &get_type(int id) const { return _types[id]; }
|
||||
Dictionary get_type_info_dict(int id) const;
|
||||
const NodeType &get_type(uint32_t id) const { return _types[id]; }
|
||||
Dictionary get_type_info_dict(uint32_t id) const;
|
||||
|
||||
private:
|
||||
FixedArray<NodeType, VoxelGeneratorGraph::NODE_TYPE_COUNT> _types;
|
||||
|
626
generators/graph/voxel_graph_runtime.cpp
Normal file
626
generators/graph/voxel_graph_runtime.cpp
Normal file
@ -0,0 +1,626 @@
|
||||
#include "voxel_graph_runtime.h"
|
||||
#include "range_utility.h"
|
||||
#include "voxel_generator_graph.h"
|
||||
#include "voxel_graph_node_db.h"
|
||||
|
||||
//#ifdef DEBUG_ENABLED
|
||||
//#define VOXEL_DEBUG_GRAPH_PROG_SENTINEL uint16_t(12345) // 48, 57 (base 10)
|
||||
//#endif
|
||||
|
||||
//template <typename T>
|
||||
//inline void write_static(std::vector<uint8_t> &mem, uint32_t p, const T &v) {
|
||||
//#ifdef DEBUG_ENABLED
|
||||
// CRASH_COND(p + sizeof(T) >= mem.size());
|
||||
//#endif
|
||||
// *(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 const T &read(const std::vector<uint8_t> &mem, uint32_t &p) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(p + sizeof(T) > mem.size());
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
void VoxelGraphRuntime::clear() {
|
||||
_program.clear();
|
||||
_memory.resize(8, 0);
|
||||
}
|
||||
|
||||
void VoxelGraphRuntime::compile(const ProgramGraph &graph) {
|
||||
std::vector<uint32_t> order;
|
||||
std::vector<uint32_t> terminal_nodes;
|
||||
|
||||
graph.find_terminal_nodes(terminal_nodes);
|
||||
// For now only 1 end is supported
|
||||
ERR_FAIL_COND(terminal_nodes.size() != 1);
|
||||
|
||||
graph.find_dependencies(terminal_nodes.back(), order);
|
||||
|
||||
_program.clear();
|
||||
|
||||
// Main inputs X, Y, Z
|
||||
_memory.resize(3);
|
||||
|
||||
std::vector<uint8_t> &program = _program;
|
||||
const VoxelGraphNodeDB &type_db = *VoxelGraphNodeDB::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 *node = graph.get_node(node_id);
|
||||
const VoxelGraphNodeDB::NodeType &type = type_db.get_type(node->type_id);
|
||||
|
||||
CRASH_COND(node == nullptr);
|
||||
CRASH_COND(node->inputs.size() != type.inputs.size());
|
||||
CRASH_COND(node->outputs.size() != type.outputs.size());
|
||||
|
||||
switch (node->type_id) {
|
||||
case VoxelGeneratorGraph::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 VoxelGeneratorGraph::NODE_INPUT_X:
|
||||
output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 0;
|
||||
break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Y:
|
||||
output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 1;
|
||||
break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Z:
|
||||
output_port_addresses[ProgramGraph::PortLocation{ node_id, 0 }] = 2;
|
||||
break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_OUTPUT_SDF:
|
||||
// TODO Multiple outputs may be supported if we get branching
|
||||
CRASH_COND(has_output);
|
||||
has_output = true;
|
||||
break;
|
||||
|
||||
default: {
|
||||
// Add actual operation
|
||||
CRASH_COND(node->type_id > 0xff);
|
||||
append(program, static_cast<uint8_t>(node->type_id));
|
||||
|
||||
// Add inputs
|
||||
for (size_t j = 0; j < type.inputs.size(); ++j) {
|
||||
uint16_t a;
|
||||
|
||||
if (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 = 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_id) {
|
||||
|
||||
case VoxelGeneratorGraph::NODE_CURVE: {
|
||||
Ref<Curve> curve = node->params[0];
|
||||
CRASH_COND(curve.is_null());
|
||||
uint8_t is_monotonic_increasing;
|
||||
Interval range = get_curve_range(**curve, is_monotonic_increasing);
|
||||
append(program, is_monotonic_increasing);
|
||||
append(program, range.min);
|
||||
append(program, range.max);
|
||||
append(program, *curve);
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_IMAGE_2D: {
|
||||
Ref<Image> im = node->params[0];
|
||||
CRASH_COND(im.is_null());
|
||||
Interval range = get_heightmap_range(**im);
|
||||
append(program, range.min);
|
||||
append(program, range.max);
|
||||
append(program, *im);
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_NOISE_2D:
|
||||
case VoxelGeneratorGraph::NODE_NOISE_3D: {
|
||||
Ref<OpenSimplexNoise> noise = node->params[0];
|
||||
CRASH_COND(noise.is_null());
|
||||
append(program, *noise);
|
||||
} break;
|
||||
|
||||
// TODO Worth it?
|
||||
case VoxelGeneratorGraph::NODE_CLAMP:
|
||||
append(program, node->params[0].operator float());
|
||||
append(program, node->params[1].operator float());
|
||||
break;
|
||||
|
||||
case VoxelGeneratorGraph::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;
|
||||
|
||||
} // switch special params
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// Append a special value after each operation
|
||||
append(program, VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
|
||||
} break; // default
|
||||
|
||||
} // switch type
|
||||
}
|
||||
|
||||
if (_memory.size() < 4) {
|
||||
// In case there is nothing
|
||||
_memory.resize(4, 0);
|
||||
}
|
||||
|
||||
// Reserve space for range analysis
|
||||
_memory.resize(_memory.size() * 2);
|
||||
// Make it a copy to keep eventual constants at consistent adresses
|
||||
const size_t half_size = _memory.size() / 2;
|
||||
for (size_t i = 0, j = half_size; i < half_size; ++i, ++j) {
|
||||
_memory[j] = _memory[i];
|
||||
}
|
||||
|
||||
print_line(String("Compiled voxel graph. Program size: {0}b, memory size: {1}b")
|
||||
.format(varray(_program.size() * sizeof(float), _memory.size() * sizeof(float))));
|
||||
|
||||
CRASH_COND(!has_output);
|
||||
}
|
||||
|
||||
// The order of fields in the following structs matters.
|
||||
// They map the layout produced by the compilation.
|
||||
// Inputs go first, then outputs, then params (if applicable at runtime).
|
||||
// TODO Think about alignment
|
||||
|
||||
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;
|
||||
uint8_t is_monotonic_increasing;
|
||||
float min_value;
|
||||
float max_value;
|
||||
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;
|
||||
float min_value;
|
||||
float max_value;
|
||||
Image *p_image;
|
||||
};
|
||||
|
||||
float VoxelGraphRuntime::generate_single(const Vector3i &position) {
|
||||
// This part must be optimized for speed
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(_memory.size() == 0);
|
||||
#endif
|
||||
|
||||
ArraySlice<float> memory(_memory, 0, _memory.size() / 2);
|
||||
memory[0] = position.x;
|
||||
memory[1] = position.y;
|
||||
memory[2] = position.z;
|
||||
|
||||
// STL is unreadable on debug builds of Godot, because _DEBUG isn't defined
|
||||
//#ifdef DEBUG_ENABLED
|
||||
// const size_t memory_size = memory.size();
|
||||
// const size_t program_size = _program.size();
|
||||
// const float *memory_raw = memory.data();
|
||||
// const uint8_t *program_raw = (const uint8_t *)_program.data();
|
||||
//#endif
|
||||
|
||||
uint32_t pc = 0;
|
||||
while (pc < _program.size()) {
|
||||
|
||||
const uint8_t opid = _program[pc++];
|
||||
|
||||
switch (opid) {
|
||||
case VoxelGeneratorGraph::NODE_CONSTANT:
|
||||
case VoxelGeneratorGraph::NODE_INPUT_X:
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Y:
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Z:
|
||||
case VoxelGeneratorGraph::NODE_OUTPUT_SDF:
|
||||
// Not part of the runtime
|
||||
CRASH_NOW();
|
||||
break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_ADD: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
memory[n.a_out] = memory[n.a_i0] + memory[n.a_i1];
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SUBTRACT: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
memory[n.a_out] = memory[n.a_i0] - memory[n.a_i1];
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_MULTIPLY: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
memory[n.a_out] = memory[n.a_i0] * memory[n.a_i1];
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SINE: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
memory[n.a_out] = Math::sin(Math_PI * memory[n.a_in]);
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_FLOOR: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
memory[n.a_out] = Math::floor(memory[n.a_in]);
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_ABS: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
memory[n.a_out] = Math::abs(memory[n.a_in]);
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SQRT: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
memory[n.a_out] = Math::sqrt(memory[n.a_in]);
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::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 VoxelGeneratorGraph::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 VoxelGeneratorGraph::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 VoxelGeneratorGraph::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 VoxelGeneratorGraph::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 VoxelGeneratorGraph::NODE_CURVE: {
|
||||
const PNodeCurve &n = read<PNodeCurve>(_program, pc);
|
||||
memory[n.a_out] = n.p_curve->interpolate_baked(memory[n.a_in]);
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::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 VoxelGeneratorGraph::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 VoxelGeneratorGraph::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;
|
||||
}
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// If this fails, the program is ill-formed
|
||||
CRASH_COND(read<uint16_t>(_program, pc) != VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
return memory[memory.size() - 1];
|
||||
}
|
||||
|
||||
Interval VoxelGraphRuntime::analyze_range(Vector3i min_pos, Vector3i max_pos) {
|
||||
ArraySlice<float> min_memory(_memory, 0, _memory.size() / 2);
|
||||
ArraySlice<float> max_memory(_memory, _memory.size() / 2, _memory.size());
|
||||
min_memory[0] = min_pos.x;
|
||||
min_memory[1] = min_pos.y;
|
||||
min_memory[2] = min_pos.z;
|
||||
max_memory[0] = max_pos.x;
|
||||
max_memory[1] = max_pos.y;
|
||||
max_memory[2] = max_pos.z;
|
||||
|
||||
uint32_t pc = 0;
|
||||
while (pc < _program.size()) {
|
||||
|
||||
const uint8_t opid = _program[pc++];
|
||||
|
||||
switch (opid) {
|
||||
case VoxelGeneratorGraph::NODE_CONSTANT:
|
||||
case VoxelGeneratorGraph::NODE_INPUT_X:
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Y:
|
||||
case VoxelGeneratorGraph::NODE_INPUT_Z:
|
||||
case VoxelGeneratorGraph::NODE_OUTPUT_SDF:
|
||||
// Not part of the runtime
|
||||
CRASH_NOW();
|
||||
break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_ADD: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
min_memory[n.a_out] = min_memory[n.a_i0] + min_memory[n.a_i1];
|
||||
max_memory[n.a_out] = max_memory[n.a_i0] + max_memory[n.a_i1];
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SUBTRACT: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
min_memory[n.a_out] = min_memory[n.a_i0] - max_memory[n.a_i1];
|
||||
max_memory[n.a_out] = max_memory[n.a_i0] - min_memory[n.a_i1];
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_MULTIPLY: {
|
||||
const PNodeBinop &n = read<PNodeBinop>(_program, pc);
|
||||
Interval r = Interval(min_memory[n.a_i0], max_memory[n.a_i0]) *
|
||||
Interval(min_memory[n.a_i1], max_memory[n.a_i1]);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SINE: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
Interval r = sin(Interval(min_memory[n.a_in], max_memory[n.a_in]) * Math_PI);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_FLOOR: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
// Floor is monotonic so I guess we can just do that
|
||||
min_memory[n.a_out] = Math::floor(min_memory[n.a_in]);
|
||||
max_memory[n.a_out] = Math::floor(max_memory[n.a_in]); // ceil?
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_ABS: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
Interval r = abs(Interval(min_memory[n.a_in], max_memory[n.a_in]));
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_SQRT: {
|
||||
const PNodeMonoFunc &n = read<PNodeMonoFunc>(_program, pc);
|
||||
Interval r = sqrt(Interval(min_memory[n.a_in], max_memory[n.a_in]));
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_DISTANCE_2D: {
|
||||
const PNodeDistance2D &n = read<PNodeDistance2D>(_program, pc);
|
||||
Interval x0(min_memory[n.a_x0], max_memory[n.a_x0]);
|
||||
Interval y0(min_memory[n.a_y0], max_memory[n.a_y0]);
|
||||
Interval x1(min_memory[n.a_x1], max_memory[n.a_x1]);
|
||||
Interval y1(min_memory[n.a_y1], max_memory[n.a_y1]);
|
||||
Interval dx = x1 - x0;
|
||||
Interval dy = y1 - y0;
|
||||
Interval r = sqrt(dx * dx + dy * dy);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_DISTANCE_3D: {
|
||||
const PNodeDistance3D &n = read<PNodeDistance3D>(_program, pc);
|
||||
Interval x0(min_memory[n.a_x0], max_memory[n.a_x0]);
|
||||
Interval y0(min_memory[n.a_y0], max_memory[n.a_y0]);
|
||||
Interval z0(min_memory[n.a_z0], max_memory[n.a_z0]);
|
||||
Interval x1(min_memory[n.a_x1], max_memory[n.a_x1]);
|
||||
Interval y1(min_memory[n.a_y1], max_memory[n.a_y1]);
|
||||
Interval z1(min_memory[n.a_z1], max_memory[n.a_z1]);
|
||||
Interval dx = x1 - x0;
|
||||
Interval dy = y1 - y0;
|
||||
Interval dz = z1 - z0;
|
||||
Interval r = sqrt(dx * dx + dy * dy + dz * dz);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_MIX: {
|
||||
const PNodeMix &n = read<PNodeMix>(_program, pc);
|
||||
Interval a(min_memory[n.a_i0], max_memory[n.a_i0]);
|
||||
Interval b(min_memory[n.a_i1], max_memory[n.a_i1]);
|
||||
Interval t(min_memory[n.a_ratio], max_memory[n.a_ratio]);
|
||||
Interval r = lerp(a, b, t);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_CLAMP: {
|
||||
const PNodeClamp &n = read<PNodeClamp>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
// TODO We may want to have wirable min and max later
|
||||
Interval cmin = Interval::from_single_value(n.p_min);
|
||||
Interval cmax = Interval::from_single_value(n.p_max);
|
||||
Interval r = clamp(x, cmin, cmax);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_REMAP: {
|
||||
const PNodeRemap &n = read<PNodeRemap>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
Interval r = ((x - n.p_c0) * n.p_m0) * n.p_m1 + n.p_c1;
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_CURVE: {
|
||||
const PNodeCurve &n = read<PNodeCurve>(_program, pc);
|
||||
if (min_memory[n.a_in] == max_memory[n.a_in]) {
|
||||
float v = n.p_curve->interpolate_baked(min_memory[n.a_in]);
|
||||
min_memory[n.a_out] = v;
|
||||
max_memory[n.a_out] = v;
|
||||
} else if (n.is_monotonic_increasing) {
|
||||
min_memory[n.a_out] = n.p_curve->interpolate_baked(min_memory[n.a_in]);
|
||||
max_memory[n.a_out] = n.p_curve->interpolate_baked(max_memory[n.a_in]);
|
||||
} else {
|
||||
// TODO Segment the curve?
|
||||
min_memory[n.a_out] = n.min_value;
|
||||
max_memory[n.a_out] = n.max_value;
|
||||
}
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_NOISE_2D: {
|
||||
const PNodeNoise2D &n = read<PNodeNoise2D>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
Interval y(min_memory[n.a_y], max_memory[n.a_y]);
|
||||
Interval r = get_osn_range_2d(n.p_noise, x, y);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_NOISE_3D: {
|
||||
const PNodeNoise3D &n = read<PNodeNoise3D>(_program, pc);
|
||||
Interval x(min_memory[n.a_x], max_memory[n.a_x]);
|
||||
Interval y(min_memory[n.a_y], max_memory[n.a_y]);
|
||||
Interval z(min_memory[n.a_z], max_memory[n.a_z]);
|
||||
Interval r = get_osn_range_3d(n.p_noise, x, y, z);
|
||||
min_memory[n.a_out] = r.min;
|
||||
max_memory[n.a_out] = r.max;
|
||||
} break;
|
||||
|
||||
case VoxelGeneratorGraph::NODE_IMAGE_2D: {
|
||||
const PNodeImage2D &n = read<PNodeImage2D>(_program, pc);
|
||||
// TODO Segment image?
|
||||
min_memory[n.a_out] = n.min_value;
|
||||
max_memory[n.a_out] = n.max_value;
|
||||
} break;
|
||||
|
||||
default:
|
||||
CRASH_NOW();
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef VOXEL_DEBUG_GRAPH_PROG_SENTINEL
|
||||
// If this fails, the program is ill-formed
|
||||
CRASH_COND(read<uint16_t>(_program, pc) != VOXEL_DEBUG_GRAPH_PROG_SENTINEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
return Interval(min_memory[min_memory.size() - 1], max_memory[max_memory.size() - 1]);
|
||||
}
|
20
generators/graph/voxel_graph_runtime.h
Normal file
20
generators/graph/voxel_graph_runtime.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef VOXEL_GRAPH_RUNTIME_H
|
||||
#define VOXEL_GRAPH_RUNTIME_H
|
||||
|
||||
#include "../../math/interval.h"
|
||||
#include "../../math/vector3i.h"
|
||||
#include "program_graph.h"
|
||||
|
||||
class VoxelGraphRuntime {
|
||||
public:
|
||||
void clear();
|
||||
void compile(const ProgramGraph &graph);
|
||||
float generate_single(const Vector3i &position);
|
||||
Interval analyze_range(Vector3i min_pos, Vector3i max_pos);
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> _program;
|
||||
std::vector<float> _memory;
|
||||
};
|
||||
|
||||
#endif // VOXEL_GRAPH_RUNTIME_H
|
@ -124,6 +124,7 @@ struct Vector3i {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Deprecate
|
||||
_FORCE_INLINE_ bool is_contained_in(const Vector3i &min, const Vector3i &max) {
|
||||
return x >= min.x && y >= min.y && z >= min.z && x < max.x && y < max.y && z < max.z;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user