godot_voxel/generators/graph/voxel_graph_node_db.cpp
2021-02-21 18:18:01 +00:00

1539 lines
49 KiB
C++

#include "voxel_graph_node_db.h"
#include "../../util/math/sdf.h"
#include "../../util/noise/fast_noise_lite.h"
#include "../../util/profiling.h"
#include "image_range_grid.h"
#include "range_utility.h"
#include <modules/opensimplex/open_simplex_noise.h>
#include <scene/resources/curve.h>
namespace {
VoxelGraphNodeDB *g_node_type_db = nullptr;
}
template <typename F>
inline void do_monop(VoxelGraphRuntime::ProcessBufferContext &ctx, F f) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < a.size; ++i) {
out.data[i] = f(a.data[i]);
}
}
template <typename F>
inline void do_binop(VoxelGraphRuntime::ProcessBufferContext &ctx, F f) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &b = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const uint32_t buffer_size = out.size;
if (a.is_constant || b.is_constant) {
float c;
const float *v;
if (a.is_constant) {
c = a.constant_value;
v = b.data;
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = f(c, v[i]);
}
} else {
c = b.constant_value;
v = a.data;
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = f(v[i], c);
}
}
} else {
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = f(a.data[i], b.data[i]);
}
}
}
void do_division(VoxelGraphRuntime::ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &b = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const uint32_t buffer_size = out.size;
if (a.is_constant || b.is_constant) {
float c;
const float *v;
if (a.is_constant) {
c = a.constant_value;
v = b.data;
for (uint32_t i = 0; i < buffer_size; ++i) {
if (b.data[i] == 0.f) {
out.data[i] = 0.f;
} else {
out.data[i] = c / v[i];
}
}
} else {
c = b.constant_value;
v = a.data;
if (c == 0.f) {
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = 0.f;
}
} else {
c = 1.f / c;
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = v[i] * c;
}
}
}
} else {
for (uint32_t i = 0; i < buffer_size; ++i) {
if (b.data[i] == 0.f) {
out.data[i] = 0.f;
} else {
out.data[i] = a.data[i] / b.data[i];
}
}
}
}
inline float get_pixel_repeat(const Image &im, int x, int y) {
return im.get_pixel(wrap(x, im.get_width()), wrap(y, im.get_height())).r;
}
inline float get_pixel_repeat_linear(const Image &im, float x, float y) {
const int x0 = int(Math::floor(x));
const int y0 = int(Math::floor(y));
const float xf = x - x0;
const float yf = y - y0;
const float h00 = get_pixel_repeat(im, x0, y0);
const float h10 = get_pixel_repeat(im, x0 + 1, y0);
const float h01 = get_pixel_repeat(im, x0, y0 + 1);
const float h11 = get_pixel_repeat(im, x0 + 1, y0 + 1);
// Bilinear filter
const float h = Math::lerp(Math::lerp(h00, h10, xf), Math::lerp(h01, h11, xf), yf);
return h;
}
inline float select(float a, float b, float threshold, float t) {
return t < threshold ? a : b;
}
inline Interval select(const Interval &a, const Interval &b, const Interval &threshold, const Interval &t) {
if (t.max < threshold.min) {
return a;
}
if (t.min >= threshold.max) {
return b;
}
return Interval(min(a.min, b.min), max(a.max, b.max));
}
template <typename T>
inline T skew3(T x) {
return (x * x * x + x) * 0.5f;
}
// This is mostly useful for generating planets from an existing heightmap
inline float sdf_sphere_heightmap(float x, float y, float z, float r, float m, const Image &im,
float min_h, float max_h, float norm_x, float norm_y) {
const float d = Math::sqrt(x * x + y * y + z * z) + 0.0001f;
const float sd = d - r;
// Optimize when far enough from heightmap.
// This introduces a discontinuity but it should be ok for clamped storage
const float margin = 1.2f * (max_h - min_h);
if (sd > max_h + margin || sd < min_h - margin) {
return sd;
}
const float nx = x / d;
const float ny = y / d;
const float nz = z / d;
// TODO Could use fast atan2, it doesn't have to be precise
// https://github.com/ducha-aiki/fast_atan2/blob/master/fast_atan.cpp
const float uvx = -Math::atan2(nz, nx) * VoxelConstants::INV_TAU + 0.5f;
// This is an approximation of asin(ny)/(PI/2)
// TODO It may be desirable to use the real function though,
// in cases where we want to combine the same map in shaders
const float ys = skew3(ny);
const float uvy = -0.5f * ys + 0.5f;
// TODO Could use bicubic interpolation when the image is sampled at lower resolution than voxels
const float h = get_pixel_repeat_linear(im, uvx * norm_x, uvy * norm_y);
return sd - m * h;
}
inline Interval sdf_sphere_heightmap(Interval x, Interval y, Interval z, float r, float m,
const ImageRangeGrid *im_range, float norm_x, float norm_y) {
const Interval d = get_length(x, y, z) + 0.0001f;
const Interval sd = d - r;
// TODO There is a discontinuity here due to the optimization done in the regular function
// Not sure yet how to implement it here. Worst case scenario, we remove it
const Interval nx = x / d;
const Interval ny = y / d;
const Interval nz = z / d;
const Interval ys = skew3(ny);
const Interval uvy = -0.5f * ys + 0.5f;
// atan2 returns results between -PI and PI but sometimes the angle can wrap, we have to account for this
OptionalInterval atan_r1;
const Interval atan_r0 = atan2(nz, nx, &atan_r1);
Interval h;
{
const Interval uvx = -atan_r0 * VoxelConstants::INV_TAU + 0.5f;
h = im_range->get_range(uvx * norm_x, uvy * norm_y);
}
if (atan_r1.valid) {
const Interval uvx = -atan_r1.value * VoxelConstants::INV_TAU + 0.5f;
h.add_interval(im_range->get_range(uvx * norm_x, uvy * norm_y));
}
return sd - m * h;
}
VoxelGraphNodeDB *VoxelGraphNodeDB::get_singleton() {
CRASH_COND(g_node_type_db == nullptr);
return g_node_type_db;
}
void VoxelGraphNodeDB::create_singleton() {
CRASH_COND(g_node_type_db != nullptr);
g_node_type_db = memnew(VoxelGraphNodeDB());
}
void VoxelGraphNodeDB::destroy_singleton() {
CRASH_COND(g_node_type_db == nullptr);
memdelete(g_node_type_db);
g_node_type_db = nullptr;
}
const char *VoxelGraphNodeDB::get_category_name(Category category) {
switch (category) {
case CATEGORY_INPUT:
return "Input";
case CATEGORY_OUTPUT:
return "Output";
case CATEGORY_MATH:
return "Math";
case CATEGORY_CONVERT:
return "Convert";
case CATEGORY_GENERATE:
return "Generate";
case CATEGORY_SDF:
return "Sdf";
case CATEGORY_DEBUG:
return "Debug";
default:
CRASH_NOW_MSG("Unhandled category");
}
return "";
}
VoxelGraphNodeDB::VoxelGraphNodeDB() {
typedef VoxelGraphRuntime::CompileContext CompileContext;
typedef VoxelGraphRuntime::ProcessBufferContext ProcessBufferContext;
typedef VoxelGraphRuntime::RangeAnalysisContext RangeAnalysisContext;
FixedArray<NodeType, VoxelGeneratorGraph::NODE_TYPE_COUNT> &types = _types;
// TODO Most operations need SIMD support
// SUGG the program could be a list of pointers to polymorphic heap-allocated classes...
// but I find that the data struct approach is kinda convenient too?
{
NodeType &t = types[VoxelGeneratorGraph::NODE_CONSTANT];
t.name = "Constant";
t.category = CATEGORY_INPUT;
t.outputs.push_back(Port("value"));
t.params.push_back(Param("value", Variant::REAL));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_INPUT_X];
t.name = "InputX";
t.category = CATEGORY_INPUT;
t.outputs.push_back(Port("x"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_INPUT_Y];
t.name = "InputY";
t.category = CATEGORY_INPUT;
t.outputs.push_back(Port("y"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_INPUT_Z];
t.name = "InputZ";
t.category = CATEGORY_INPUT;
t.outputs.push_back(Port("z"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_OUTPUT_SDF];
t.name = "OutputSDF";
t.category = CATEGORY_OUTPUT;
t.inputs.push_back(Port("sdf"));
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_ADD];
t.name = "Add";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("out"));
t.compile_func = nullptr;
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return a + b; });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, a + b);
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SUBTRACT];
t.name = "Subtract";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return a - b; });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, a - b);
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_MULTIPLY];
t.name = "Multiply";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return a * b; });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, a * b);
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_DIVIDE];
t.name = "Divide";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = do_division;
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, a / b);
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SIN];
t.name = "Sin";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_monop(ctx, [](float a) { return Math::sin(a); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
ctx.set_output(0, sin(a));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_FLOOR];
t.name = "Floor";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_monop(ctx, [](float a) { return Math::floor(a); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
ctx.set_output(0, floor(a));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_ABS];
t.name = "Abs";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_monop(ctx, [](float a) { return Math::abs(a); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
ctx.set_output(0, abs(a));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SQRT];
t.name = "Sqrt";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_monop(ctx, [](float a) { return Math::sqrt(a); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
ctx.set_output(0, sqrt(a));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_FRACT];
t.name = "Fract";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_monop(ctx, [](float a) { return a - Math::floor(a); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
ctx.set_output(0, a - floor(a));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_STEPIFY];
t.name = "Stepify";
t.category = CATEGORY_CONVERT;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("step"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return Math::stepify(a, b); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, stepify(a, b));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_WRAP];
t.name = "Wrap";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("length"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return wrapf(a, b); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, wrapf(a, b));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_MIN];
t.name = "Min";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return min(a, b); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, min_interval(a, b));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_MAX];
t.name = "Max";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return max(a, b); });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, max_interval(a, b));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_DISTANCE_2D];
t.name = "Distance2D";
t.category = CATEGORY_MATH;
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("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &x0 = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y0 = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &x1 = ctx.get_input(2);
const VoxelGraphRuntime::Buffer &y1 = ctx.get_input(3);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = Math::sqrt(
squared(x1.data[i] - x0.data[i]) +
squared(y1.data[i] - y0.data[i]));
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x0 = ctx.get_input(0);
const Interval y0 = ctx.get_input(1);
const Interval x1 = ctx.get_input(2);
const Interval y1 = ctx.get_input(3);
const Interval dx = x1 - x0;
const Interval dy = y1 - y0;
const Interval r = sqrt(dx * dx + dy * dy);
ctx.set_output(0, r);
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_DISTANCE_3D];
t.name = "Distance3D";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x0"));
t.inputs.push_back(Port("y0"));
t.inputs.push_back(Port("z0"));
t.inputs.push_back(Port("x1"));
t.inputs.push_back(Port("y1"));
t.inputs.push_back(Port("z1"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &x0 = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y0 = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &z0 = ctx.get_input(2);
const VoxelGraphRuntime::Buffer &x1 = ctx.get_input(3);
const VoxelGraphRuntime::Buffer &y1 = ctx.get_input(4);
const VoxelGraphRuntime::Buffer &z1 = ctx.get_input(5);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = Math::sqrt(
squared(x1.data[i] - x0.data[i]) +
squared(y1.data[i] - y0.data[i]) +
squared(z1.data[i] - z0.data[i]));
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x0 = ctx.get_input(0);
const Interval y0 = ctx.get_input(1);
const Interval z0 = ctx.get_input(2);
const Interval x1 = ctx.get_input(3);
const Interval y1 = ctx.get_input(4);
const Interval z1 = ctx.get_input(5);
const Interval dx = x1 - x0;
const Interval dy = y1 - y0;
const Interval dz = z1 - z0;
Interval r = sqrt(dx * dx + dy * dy + dz * dz);
ctx.set_output(0, r);
};
}
{
struct Params {
float min;
float max;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_CLAMP];
t.name = "Clamp";
t.category = CATEGORY_CONVERT;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("min", Variant::REAL, -1.f));
t.params.push_back(Param("max", Variant::REAL, 1.f));
t.compile_func = [](CompileContext &ctx) {
Params p;
p.min = ctx.get_param(0).operator float();
p.max = ctx.get_param(1).operator float();
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = clamp(a.data[i], p.min, p.max);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Params p = ctx.get_params<Params>();
const Interval cmin = Interval::from_single_value(p.min);
const Interval cmax = Interval::from_single_value(p.max);
ctx.set_output(0, clamp(a, cmin, cmax));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_MIX];
t.name = "Mix";
t.category = CATEGORY_CONVERT;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.inputs.push_back(Port("ratio"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &b = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &r = ctx.get_input(2);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const uint32_t buffer_size = out.size;
if (a.is_constant) {
const float ca = a.constant_value;
if (b.is_constant) {
const float cb = b.constant_value;
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = Math::lerp(ca, cb, r.data[i]);
}
} else {
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = Math::lerp(ca, b.data[i], r.data[i]);
}
}
} else if (b.is_constant) {
const float cb = b.constant_value;
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = Math::lerp(a.data[i], cb, r.data[i]);
}
} else {
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = Math::lerp(a.data[i], b.data[i], r.data[i]);
}
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
// Note: if I call this `t` like I use to do with `lerp`, GCC complains it shadows `t` from the outer scope.
// Even though this lambda does not capture anything from the outer scope :shrug:
const Interval r = ctx.get_input(2);
ctx.set_output(0, lerp(a, b, r));
};
}
{
struct Params {
float c0;
float c1;
float m0;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_REMAP];
t.name = "Remap";
t.category = CATEGORY_CONVERT;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("min0", Variant::REAL, -1.f));
t.params.push_back(Param("max0", Variant::REAL, 1.f));
t.params.push_back(Param("min1", Variant::REAL, -1.f));
t.params.push_back(Param("max1", Variant::REAL, 1.f));
t.compile_func = [](CompileContext &ctx) {
Params p;
const float min0 = ctx.get_param(0).operator float();
const float max0 = ctx.get_param(1).operator float();
const float min1 = ctx.get_param(2).operator float();
const float max1 = ctx.get_param(3).operator float();
p.c0 = -min0;
p.m0 = (max1 - min1) * (Math::is_equal_approx(max0, min0) ? 99999.f : 1.f / (max0 - min0));
p.c1 = min1;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = (a.data[i] - p.c0) * p.m0 + p.c1;
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Params p = ctx.get_params<Params>();
ctx.set_output(0, (a - p.c0) * p.m0 + p.c1);
};
}
{
struct Params {
float edge0;
float edge1;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_SMOOTHSTEP];
t.name = "Smoothstep";
t.category = CATEGORY_CONVERT;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("edge0", Variant::REAL, 0.f));
t.params.push_back(Param("edge1", Variant::REAL, 1.f));
t.compile_func = [](CompileContext &ctx) {
Params p;
p.edge0 = ctx.get_param(0).operator float();
p.edge1 = ctx.get_param(1).operator float();
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = smoothstep(p.edge0, p.edge1, a.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Params p = ctx.get_params<Params>();
ctx.set_output(0, smoothstep(p.edge0, p.edge1, a));
};
}
{
struct Params {
float min_value;
float max_value;
// TODO Should be `const` but isn't because it auto-bakes, and it's a concern for multithreading
Curve *curve;
bool is_monotonic_increasing;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_CURVE];
t.name = "Curve";
t.category = CATEGORY_CONVERT;
t.inputs.push_back(Port("x"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("curve", "Curve"));
t.compile_func = [](CompileContext &ctx) {
Ref<Curve> curve = ctx.get_param(0);
if (curve.is_null()) {
ctx.make_error("Curve instance is null");
return;
}
// Make sure it is baked. We don't want multithreading to bail out because of a write operation
// happening in `interpolate_baked`...
curve->bake();
bool is_monotonic_increasing;
const Interval range = get_curve_range(**curve, is_monotonic_increasing);
Params p;
p.is_monotonic_increasing = is_monotonic_increasing;
p.min_value = range.min;
p.max_value = range.max;
p.curve = *curve;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_CURVE");
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = p.curve->interpolate_baked(a.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Params p = ctx.get_params<Params>();
if (a.is_single_value()) {
const float v = p.curve->interpolate_baked(a.min);
ctx.set_output(0, Interval::from_single_value(v));
} else if (p.is_monotonic_increasing) {
ctx.set_output(0, Interval(p.curve->interpolate_baked(a.min), p.curve->interpolate_baked(a.max)));
} else {
// TODO Segment the curve?
ctx.set_output(0, Interval(p.min_value, p.max_value));
}
};
}
{
struct Params {
// TODO Should be `const` but isn't because of an oversight in Godot
OpenSimplexNoise *noise;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_NOISE_2D];
t.name = "Noise2D";
t.category = CATEGORY_GENERATE;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("noise", "OpenSimplexNoise"));
t.compile_func = [](CompileContext &ctx) {
Ref<OpenSimplexNoise> noise = ctx.get_param(0);
if (noise.is_null()) {
ctx.make_error("OpenSimplexNoise instance is null");
return;
}
Params p;
p.noise = *noise;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_NOISE_2D");
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = p.noise->get_noise_2d(x.data[i], y.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Params p = ctx.get_params<Params>();
ctx.set_output(0, get_osn_range_2d(p.noise, x, y));
};
}
{
struct Params {
// TODO Should be `const` but isn't because of an oversight in Godot
OpenSimplexNoise *noise;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_NOISE_3D];
t.name = "Noise3D";
t.category = CATEGORY_GENERATE;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("noise", "OpenSimplexNoise"));
t.compile_func = [](CompileContext &ctx) {
Ref<OpenSimplexNoise> noise = ctx.get_param(0);
if (noise.is_null()) {
ctx.make_error("OpenSimplexNoise instance is null");
return;
}
Params p;
p.noise = *noise;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_NOISE_3D");
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &z = ctx.get_input(2);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = p.noise->get_noise_3d(x.data[i], y.data[i], z.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Params p = ctx.get_params<Params>();
ctx.set_output(0, get_osn_range_3d(p.noise, x, y, z));
};
}
{
struct Params {
const Image *image;
const ImageRangeGrid *image_range_grid;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_IMAGE_2D];
t.name = "Image";
t.category = CATEGORY_GENERATE;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("image", "Image"));
t.compile_func = [](CompileContext &ctx) {
Ref<Image> image = ctx.get_param(0);
if (image.is_null()) {
ctx.make_error("Image instance is null");
return;
}
ImageRangeGrid *im_range = memnew(ImageRangeGrid);
im_range->generate(**image);
Params p;
p.image = *image;
p.image_range_grid = im_range;
ctx.set_params(p);
ctx.add_memdelete_cleanup(im_range);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_IMAGE_2D");
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
// TODO Allow to use bilinear filtering?
const Params p = ctx.get_params<Params>();
const Image &im = *p.image;
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = get_pixel_repeat(im, x.data[i], y.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Params p = ctx.get_params<Params>();
ctx.set_output(0, p.image_range_grid->get_range(x, y));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_PLANE];
t.name = "SdfPlane";
t.category = CATEGORY_SDF;
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("height"));
t.outputs.push_back(Port("sdf"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
do_binop(ctx, [](float a, float b) { return a - b; });
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
ctx.set_output(0, a - b);
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_BOX];
t.name = "SdfBox";
t.category = CATEGORY_SDF;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.inputs.push_back(Port("size_x"));
t.inputs.push_back(Port("size_y"));
t.inputs.push_back(Port("size_z"));
t.outputs.push_back(Port("sdf"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &z = ctx.get_input(2);
const VoxelGraphRuntime::Buffer &sx = ctx.get_input(3);
const VoxelGraphRuntime::Buffer &sy = ctx.get_input(4);
const VoxelGraphRuntime::Buffer &sz = ctx.get_input(5);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = sdf_box(
Vector3(x.data[i], y.data[i], z.data[i]),
Vector3(sx.data[i], sy.data[i], sz.data[i]));
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Interval sx = ctx.get_input(3);
const Interval sy = ctx.get_input(4);
const Interval sz = ctx.get_input(5);
ctx.set_output(0, sdf_box(x, y, z, sx, sy, sz));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_SPHERE];
t.name = "SdfSphere";
t.category = CATEGORY_SDF;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.inputs.push_back(Port("radius"));
t.outputs.push_back(Port("sdf"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &z = ctx.get_input(2);
const VoxelGraphRuntime::Buffer &r = ctx.get_input(3);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = Math::sqrt(squared(x.data[i]) + squared(y.data[i]) + squared(z.data[i])) - r.data[i];
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Interval r = ctx.get_input(3);
ctx.set_output(0, get_length(x, y, z) - r);
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_TORUS];
t.name = "SdfTorus";
t.category = CATEGORY_SDF;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.inputs.push_back(Port("radius1", 16.f));
t.inputs.push_back(Port("radius2", 4.f));
t.outputs.push_back(Port("sdf"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &z = ctx.get_input(2);
const VoxelGraphRuntime::Buffer &r0 = ctx.get_input(3);
const VoxelGraphRuntime::Buffer &r1 = ctx.get_input(4);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = sdf_torus(x.data[i], y.data[i], z.data[i], r0.data[i], r1.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Interval r0 = ctx.get_input(3);
const Interval r1 = ctx.get_input(4);
ctx.set_output(0, sdf_torus(x, y, z, r0, r1));
};
}
{
struct Params {
float smoothness;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_SMOOTH_UNION];
t.name = "SdfSmoothUnion";
t.category = CATEGORY_SDF;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("sdf"));
t.params.push_back(Param("smoothness", Variant::REAL, 0.f));
t.compile_func = [](CompileContext &ctx) {
Params p;
p.smoothness = ctx.get_param(0).operator float();
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_SDF_SMOOTH_UNION");
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &b = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params params = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = sdf_smooth_union(a.data[i], b.data[i], params.smoothness);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
const Params params = ctx.get_params<Params>();
ctx.set_output(0, sdf_smooth_union(a, b, Interval::from_single_value(params.smoothness)));
};
}
{
struct Params {
float smoothness;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_SMOOTH_SUBTRACT];
t.name = "SdfSmoothSubtract";
t.category = CATEGORY_SDF;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.outputs.push_back(Port("sdf"));
t.params.push_back(Param("smoothness", Variant::REAL, 0.f));
t.compile_func = [](CompileContext &ctx) {
Params p;
p.smoothness = ctx.get_param(0).operator float();
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_SDF_SMOOTH_SUBTRACT");
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &b = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params params = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = sdf_smooth_subtract(a.data[i], b.data[i], params.smoothness);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
const Params params = ctx.get_params<Params>();
ctx.set_output(0, sdf_smooth_subtract(a, b, Interval::from_single_value(params.smoothness)));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_PREVIEW];
t.name = "SdfPreview";
t.category = CATEGORY_DEBUG;
t.inputs.push_back(Port("value"));
t.params.push_back(Param("min_value", Variant::REAL, -1.f));
t.params.push_back(Param("max_value", Variant::REAL, 1.f));
t.debug_only = true;
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_SELECT];
t.name = "Select";
t.category = CATEGORY_CONVERT;
t.inputs.push_back(Port("a"));
t.inputs.push_back(Port("b"));
t.inputs.push_back(Port("threshold"));
t.inputs.push_back(Port("t"));
t.outputs.push_back(Port("out"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
const VoxelGraphRuntime::Buffer &a = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &b = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &threshold = ctx.get_input(2);
const VoxelGraphRuntime::Buffer &tested_value = ctx.get_input(3);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const uint32_t buffer_size = out.size;
if (tested_value.is_constant && threshold.is_constant) {
const float *src = tested_value.constant_value < threshold.constant_value ? a.data : b.data;
for (uint32_t i = 0; i < buffer_size; ++i) {
memcpy(out.data, src, buffer_size * sizeof(float));
}
} else if (a.is_constant && b.is_constant && a.constant_value == b.constant_value) {
for (uint32_t i = 0; i < buffer_size; ++i) {
memcpy(out.data, a.data, buffer_size * sizeof(float));
}
} else {
for (uint32_t i = 0; i < buffer_size; ++i) {
out.data[i] = select(a.data[i], b.data[i], threshold.data[i], tested_value.data[i]);
}
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval a = ctx.get_input(0);
const Interval b = ctx.get_input(1);
const Interval threshold = ctx.get_input(2);
const Interval tested_value = ctx.get_input(3);
ctx.set_output(0, select(a, b, threshold, tested_value));
};
}
{
struct Params {
float radius;
float factor;
float min_height;
float max_height;
float norm_x;
float norm_y;
const Image *image;
const ImageRangeGrid *image_range_grid;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_SDF_SPHERE_HEIGHTMAP];
t.name = "SdfSphereHeightmap";
t.category = CATEGORY_SDF;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.outputs.push_back(Port("sdf"));
t.params.push_back(Param("image", "Image"));
t.params.push_back(Param("radius", Variant::REAL, 10.f));
t.params.push_back(Param("factor", Variant::REAL, 1.f));
t.compile_func = [](CompileContext &ctx) {
Ref<Image> image = ctx.get_param(0);
if (image.is_null()) {
ctx.make_error("Image instance is null");
return;
}
ImageRangeGrid *im_range = memnew(ImageRangeGrid);
im_range->generate(**image);
const float factor = ctx.get_param(2);
const Interval range = im_range->get_range() * factor;
Params p;
p.min_height = range.min;
p.max_height = range.max;
p.image = *image;
p.image_range_grid = im_range;
p.radius = ctx.get_param(1);
p.factor = factor;
p.norm_x = image->get_width();
p.norm_y = image->get_height();
ctx.set_params(p);
ctx.add_memdelete_cleanup(im_range);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_SDF_SPHERE_HEIGHTMAP");
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &z = ctx.get_input(2);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
// TODO Allow to use bilinear filtering?
const Params p = ctx.get_params<Params>();
const Image &im = *p.image;
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = sdf_sphere_heightmap(x.data[i], y.data[i], z.data[i],
p.radius, p.factor, im, p.min_height, p.max_height, p.norm_x, p.norm_y);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Params p = ctx.get_params<Params>();
ctx.set_output(0,
sdf_sphere_heightmap(x, y, z, p.radius, p.factor, p.image_range_grid, p.norm_x, p.norm_y));
};
}
{
NodeType &t = types[VoxelGeneratorGraph::NODE_NORMALIZE_3D];
t.name = "Normalize";
t.category = CATEGORY_MATH;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.outputs.push_back(Port("nx"));
t.outputs.push_back(Port("ny"));
t.outputs.push_back(Port("nz"));
t.outputs.push_back(Port("len"));
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_NORMALIZE_3D");
const VoxelGraphRuntime::Buffer &xb = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &yb = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &zb = ctx.get_input(2);
VoxelGraphRuntime::Buffer &out_nx = ctx.get_output(0);
VoxelGraphRuntime::Buffer &out_ny = ctx.get_output(1);
VoxelGraphRuntime::Buffer &out_nz = ctx.get_output(2);
VoxelGraphRuntime::Buffer &out_len = ctx.get_output(3);
const uint32_t buffer_size = out_nx.size;
for (uint32_t i = 0; i < buffer_size; ++i) {
const float x = xb.data[i];
const float y = yb.data[i];
const float z = zb.data[i];
const float len = Math::sqrt(squared(x) + squared(y) + squared(z));
out_nx.data[i] = x / len;
out_ny.data[i] = y / len;
out_nz.data[i] = z / len;
out_len.data[i] = len;
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Interval len = sqrt(x * x + y * y + z * z);
const Interval nx = x / len;
const Interval ny = y / len;
const Interval nz = z / len;
ctx.set_output(0, nx);
ctx.set_output(1, ny);
ctx.set_output(2, nz);
ctx.set_output(3, len);
};
}
{
struct Params {
FastNoiseLite *noise;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_FAST_NOISE_2D];
t.name = "FastNoise2D";
t.category = CATEGORY_GENERATE;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("noise", "FastNoiseLite"));
t.compile_func = [](CompileContext &ctx) {
Ref<FastNoiseLite> noise = ctx.get_param(0);
if (noise.is_null()) {
ctx.make_error("FastNoiseLite instance is null");
return;
}
Params p;
p.noise = *noise;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_FAST_NOISE_2D");
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = p.noise->get_noise_2d(x.data[i], y.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Params p = ctx.get_params<Params>();
ctx.set_output(0, get_fnl_range_2d(p.noise, x, y));
};
}
{
struct Params {
FastNoiseLite *noise;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_FAST_NOISE_3D];
t.name = "FastNoise3D";
t.category = CATEGORY_GENERATE;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.outputs.push_back(Port("out"));
t.params.push_back(Param("noise", "FastNoiseLite"));
t.compile_func = [](CompileContext &ctx) {
Ref<FastNoiseLite> noise = ctx.get_param(0);
if (noise.is_null()) {
ctx.make_error("FastNoiseLite instance is null");
return;
}
Params p;
p.noise = *noise;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_FAST_NOISE_3D");
const VoxelGraphRuntime::Buffer &x = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &y = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &z = ctx.get_input(2);
VoxelGraphRuntime::Buffer &out = ctx.get_output(0);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = p.noise->get_noise_3d(x.data[i], y.data[i], z.data[i]);
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Params p = ctx.get_params<Params>();
ctx.set_output(0, get_fnl_range_3d(p.noise, x, y, z));
};
}
{
struct Params {
FastNoiseLiteGradient *noise;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_FAST_NOISE_GRADIENT_2D];
t.name = "FastNoiseGradient2D";
t.category = CATEGORY_GENERATE;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.outputs.push_back(Port("out_x"));
t.outputs.push_back(Port("out_y"));
t.params.push_back(Param("noise", "FastNoiseLiteGradient"));
t.compile_func = [](CompileContext &ctx) {
Ref<FastNoiseLiteGradient> noise = ctx.get_param(0);
if (noise.is_null()) {
ctx.make_error("FastNoiseLiteGradient instance is null");
return;
}
Params p;
p.noise = *noise;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_FAST_NOISE_GRADIENT_2D");
const VoxelGraphRuntime::Buffer &xb = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &yb = ctx.get_input(1);
VoxelGraphRuntime::Buffer &out_x = ctx.get_output(0);
VoxelGraphRuntime::Buffer &out_y = ctx.get_output(1);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out_x.size; ++i) {
float x = xb.data[i];
float y = yb.data[i];
p.noise->warp_2d(x, y);
out_x.data[i] = x;
out_y.data[i] = y;
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Params p = ctx.get_params<Params>();
const Interval2 r = get_fnl_gradient_range_2d(p.noise, x, y);
ctx.set_output(0, r.x);
ctx.set_output(1, r.y);
};
}
{
struct Params {
FastNoiseLiteGradient *noise;
};
NodeType &t = types[VoxelGeneratorGraph::NODE_FAST_NOISE_GRADIENT_3D];
t.name = "FastNoiseGradient3D";
t.category = CATEGORY_GENERATE;
t.inputs.push_back(Port("x"));
t.inputs.push_back(Port("y"));
t.inputs.push_back(Port("z"));
t.outputs.push_back(Port("out_x"));
t.outputs.push_back(Port("out_y"));
t.outputs.push_back(Port("out_z"));
t.params.push_back(Param("noise", "FastNoiseLiteGradient"));
t.compile_func = [](CompileContext &ctx) {
Ref<FastNoiseLiteGradient> noise = ctx.get_param(0);
if (noise.is_null()) {
ctx.make_error("FastNoiseLiteGradient instance is null");
return;
}
Params p;
p.noise = *noise;
ctx.set_params(p);
};
t.process_buffer_func = [](ProcessBufferContext &ctx) {
VOXEL_PROFILE_SCOPE_NAMED("NODE_FAST_NOISE_GRADIENT_3D");
const VoxelGraphRuntime::Buffer &xb = ctx.get_input(0);
const VoxelGraphRuntime::Buffer &yb = ctx.get_input(1);
const VoxelGraphRuntime::Buffer &zb = ctx.get_input(2);
VoxelGraphRuntime::Buffer &out_x = ctx.get_output(0);
VoxelGraphRuntime::Buffer &out_y = ctx.get_output(1);
VoxelGraphRuntime::Buffer &out_z = ctx.get_output(2);
const Params p = ctx.get_params<Params>();
for (uint32_t i = 0; i < out_x.size; ++i) {
float x = xb.data[i];
float y = yb.data[i];
float z = zb.data[i];
p.noise->warp_3d(x, y, z);
out_x.data[i] = x;
out_y.data[i] = y;
out_z.data[i] = z;
}
};
t.range_analysis_func = [](RangeAnalysisContext &ctx) {
const Interval x = ctx.get_input(0);
const Interval y = ctx.get_input(1);
const Interval z = ctx.get_input(2);
const Params p = ctx.get_params<Params>();
const Interval3 r = get_fnl_gradient_range_3d(p.noise, x, y, z);
ctx.set_output(0, r.x);
ctx.set_output(1, r.y);
ctx.set_output(2, r.z);
};
}
for (unsigned int i = 0; i < _types.size(); ++i) {
NodeType &t = _types[i];
_type_name_to_id.set(t.name, (VoxelGeneratorGraph::NodeTypeID)i);
for (size_t param_index = 0; param_index < t.params.size(); ++param_index) {
Param &p = t.params[param_index];
t.param_name_to_index.set(p.name, param_index);
p.index = param_index;
switch (p.type) {
case Variant::REAL:
if (p.default_value.get_type() == Variant::NIL) {
p.default_value = 0.f;
}
break;
case Variant::OBJECT:
break;
default:
CRASH_NOW();
break;
}
}
for (size_t input_index = 0; input_index < t.inputs.size(); ++input_index) {
const Port &p = t.inputs[input_index];
t.input_name_to_index.set(p.name, input_index);
}
}
}
Dictionary VoxelGraphNodeDB::get_type_info_dict(uint32_t id) const {
const NodeType &type = _types[id];
Dictionary type_dict;
type_dict["name"] = type.name;
Array inputs;
inputs.resize(type.inputs.size());
for (size_t i = 0; i < type.inputs.size(); ++i) {
const Port &input = type.inputs[i];
Dictionary d;
d["name"] = input.name;
inputs[i] = d;
}
Array outputs;
outputs.resize(type.outputs.size());
for (size_t i = 0; i < type.outputs.size(); ++i) {
const Port &output = type.outputs[i];
Dictionary d;
d["name"] = output.name;
outputs[i] = d;
}
Array params;
params.resize(type.params.size());
for (size_t i = 0; i < type.params.size(); ++i) {
const Param &p = type.params[i];
Dictionary d;
d["name"] = p.name;
d["type"] = p.type;
d["class_name"] = p.class_name;
d["default_value"] = p.default_value;
params[i] = d;
}
type_dict["inputs"] = inputs;
type_dict["outputs"] = outputs;
type_dict["params"] = params;
return type_dict;
}
bool VoxelGraphNodeDB::try_get_type_id_from_name(
const String &name, VoxelGeneratorGraph::NodeTypeID &out_type_id) const {
const VoxelGeneratorGraph::NodeTypeID *p = _type_name_to_id.getptr(name);
if (p == nullptr) {
return false;
}
out_type_id = *p;
return true;
}
bool VoxelGraphNodeDB::try_get_param_index_from_name(
uint32_t type_id, const String &name, uint32_t &out_param_index) const {
ERR_FAIL_INDEX_V(type_id, _types.size(), false);
const NodeType &t = _types[type_id];
const uint32_t *p = t.param_name_to_index.getptr(name);
if (p == nullptr) {
return false;
}
out_param_index = *p;
return true;
}
bool VoxelGraphNodeDB::try_get_input_index_from_name(
uint32_t type_id, const String &name, uint32_t &out_input_index) const {
ERR_FAIL_INDEX_V(type_id, _types.size(), false);
const NodeType &t = _types[type_id];
const uint32_t *p = t.input_name_to_index.getptr(name);
if (p == nullptr) {
return false;
}
out_input_index = *p;
return true;
}