#include "selection_nodes.h"
#include "../gpu/glsl_emit_context.h"
#include "../graph/graph_executor.h"
#include "../shared/blender_noise.h"
#include <godot_cpp/variant/utility_functions.hpp>
#include <cmath>
#include "../gpu/blender_hash_glsl.gen.h"
namespace godot {
// --- DeleteGeometryNode ---
void DeleteGeometryNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_delete_domain", "domain"), &DeleteGeometryNode::set_delete_domain);
ClassDB::bind_method(D_METHOD("get_delete_domain"), &DeleteGeometryNode::get_delete_domain);
ADD_PROPERTY(PropertyInfo(Variant::INT, "delete_domain"), "set_delete_domain", "get_delete_domain");
}
DeleteGeometryNode::DeleteGeometryNode() {
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
// THE SELECTION FILTER, shared by Delete Geometry and Separate Geometry.
//
// Separate Geometry is Delete Geometry run twice with the selection the other
// way up, so this is one text rather than two. Blender's Separate Geometry
// outputs the selected half AND the inverted half; ours used to be mapped onto
// SeparateComponents, which splits a geometry by COMPONENT TYPE and is a
// different node entirely.
//
// `p_invert` false keeps the UNSELECTED elements, which is what Delete does.
static Ref<ProcGeometry> _filter_by_selection(const Ref<ProcGeometry> &p_geo,
const SocketValue &p_sel, int p_domain, bool p_invert) {
if (p_geo.is_null()) {
return p_geo;
}
Ref<ProcGeometry> geo = p_geo->duplicate_geometry();
Ref<ProcMesh> mesh = geo->get_mesh();
if (mesh.is_null() || mesh->positions.size() == 0) {
return geo;
}
const int vcount = mesh->positions.size();
const int tcount = mesh->indices.size() / 3;
// Which VERTICES survive. On the point domain the selection says so
// directly; on the face domain a vertex survives if any surviving triangle
// still uses it, which is Blender's behaviour -- deleting faces takes with
// them only the vertices nothing else needs.
Vector<bool> keep_vert;
Vector<bool> keep_tri;
keep_vert.resize(vcount);
keep_tri.resize(tcount);
if (p_domain == 2) { // Face
for (int i = 0; i < vcount; i++) {
keep_vert.set(i, false);
}
const int32_t *idx = mesh->indices.ptr();
// A selection arriving on the POINT domain has to be interpolated onto
// faces before it can be read here. Blender's rule for a boolean is
// all-corners: the face is selected only if every one of its corners is.
const bool by_point = p_sel.is_field && p_sel.domain == AttrDomain::Point;
for (int t = 0; t < tcount; t++) {
bool selected;
if (by_point) {
selected = p_sel.bool_at(idx[t * 3]) && p_sel.bool_at(idx[t * 3 + 1]) &&
p_sel.bool_at(idx[t * 3 + 2]);
} else {
selected = p_sel.bool_at(t);
}
bool keep = p_invert ? selected : !selected;
keep_tri.set(t, keep);
if (keep) {
for (int k = 0; k < 3; k++) {
keep_vert.set(idx[t * 3 + k], true);
}
}
}
} else { // Point
for (int i = 0; i < vcount; i++) {
keep_vert.set(i, p_invert ? p_sel.bool_at(i) : !p_sel.bool_at(i));
}
const int32_t *idx = mesh->indices.ptr();
for (int t = 0; t < tcount; t++) {
// A triangle cannot outlive any of its corners.
keep_tri.set(t, keep_vert[idx[t * 3]] && keep_vert[idx[t * 3 + 1]] &&
keep_vert[idx[t * 3 + 2]]);
}
}
// old vertex index -> new, -1 for deleted
PackedInt32Array remap;
remap.resize(vcount);
int32_t *rw = remap.ptrw();
int kept = 0;
for (int i = 0; i < vcount; i++) {
rw[i] = keep_vert[i] ? kept++ : -1;
}
if (kept == vcount) {
bool all_tris = true;
for (int t = 0; t < tcount; t++) {
if (!keep_tri[t]) {
all_tris = false;
break;
}
}
if (all_tris) {
return geo; // nothing removed
}
}
// Compact every array that runs parallel to positions. Arrays that do not
// (tangents and face_offsets are per CORNER and per FACE) are dropped rather
// than left describing a topology that no longer exists.
PackedVector3Array pos, nrm;
PackedVector2Array uv;
PackedColorArray col;
const bool has_n = mesh->normals.size() == vcount;
const bool has_uv = mesh->uvs.size() == vcount;
const bool has_col = mesh->colors.size() == vcount;
pos.resize(kept);
if (has_n) {
nrm.resize(kept);
}
if (has_uv) {
uv.resize(kept);
}
if (has_col) {
col.resize(kept);
}
for (int i = 0; i < vcount; i++) {
int n = remap[i];
if (n < 0) {
continue;
}
pos.set(n, mesh->positions[i]);
if (has_n) {
nrm.set(n, mesh->normals[i]);
}
if (has_uv) {
uv.set(n, mesh->uvs[i]);
}
if (has_col) {
col.set(n, mesh->colors[i]);
}
}
PackedInt32Array tris;
const int32_t *idx = mesh->indices.ptr();
for (int t = 0; t < tcount; t++) {
if (!keep_tri[t]) {
continue;
}
for (int k = 0; k < 3; k++) {
tris.push_back(remap[idx[t * 3 + k]]);
}
}
mesh->positions = pos;
mesh->normals = nrm;
mesh->uvs = uv;
mesh->colors = col;
mesh->indices = tris;
mesh->tangents = PackedFloat32Array();
mesh->face_offsets = PackedInt32Array();
return geo;
}
void DeleteGeometryNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
// Selection is a FIELD, and deleting is per element. This used to read it
// with read_bool -- a scalar -- so the node deleted everything or nothing
// while advertising a selection input, which is worse than not having one.
SocketValue sel = p_ctx.read_input(p_node_idx, 1);
p_ctx.write_geometry(p_node_idx, 0, _filter_by_selection(geo, sel, delete_domain, false));
}
void SeparateGeometryNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_domain", "domain"), &SeparateGeometryNode::set_domain);
ClassDB::bind_method(D_METHOD("get_domain"), &SeparateGeometryNode::get_domain);
ADD_PROPERTY(PropertyInfo(Variant::INT, "domain"), "set_domain", "get_domain");
}
SeparateGeometryNode::SeparateGeometryNode() {
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
output_defs.push_back({ "Selection", SocketType::Geometry, Variant() });
output_defs.push_back({ "Inverted", SocketType::Geometry, Variant() });
}
void SeparateGeometryNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
SocketValue sel = p_ctx.read_input(p_node_idx, 1);
// Both halves, from the same filter with the selection the other way up.
p_ctx.write_geometry(p_node_idx, 0, _filter_by_selection(geo, sel, domain, true));
p_ctx.write_geometry(p_node_idx, 1, _filter_by_selection(geo, sel, domain, false));
}
// --- BoundingBoxNode ---
void BoundingBoxNode::_bind_methods() {
}
BoundingBoxNode::BoundingBoxNode() {
// BLENDER'S FIRST OUTPUT IS THE BOX ITSELF, as geometry. Ours began with
// Min, so a graph wiring the bounding-box MESH landed on a Vector socket,
// and Min and Max then drove our Center and Size. Use Radius is Blender's
// second input and was absent entirely.
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
input_defs.push_back({ "Use Radius", SocketType::Bool, true });
output_defs.push_back({ "Bounding Box", SocketType::Geometry, Variant() });
output_defs.push_back({ "Min", SocketType::Vector, Variant() });
output_defs.push_back({ "Max", SocketType::Vector, Variant() });
// Ours, kept AFTER Blender's three so existing Godot-authored graphs that
// read them still resolve, and so no Blender socket index can reach them.
output_defs.push_back({ "Center", SocketType::Vector, Variant() });
output_defs.push_back({ "Size", SocketType::Vector, Variant() });
}
void BoundingBoxNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
// Use Radius includes point-cloud radii in the bounds. ProcPointCloud has
// no radius attribute to include, so the input is READ (so a link to it
// lands and round-trips) and has nothing to change yet.
(void)p_ctx.read_bool(p_node_idx, 1);
if (geo.is_null() || geo->get_mesh().is_null() || geo->get_mesh()->vertex_count() == 0) {
Ref<ProcGeometry> empty;
empty.instantiate();
p_ctx.write_geometry(p_node_idx, 0, empty);
for (int i = 1; i <= 4; i++) {
p_ctx.write_vector(p_node_idx, i, Vector3());
}
return;
}
Ref<ProcMesh> mesh = geo->get_mesh();
const Vector3 *pos = mesh->positions.ptr();
int count = mesh->vertex_count();
Vector3 bb_min = pos[0];
Vector3 bb_max = pos[0];
for (int i = 1; i < count; i++) {
bb_min.x = MIN(bb_min.x, pos[i].x);
bb_min.y = MIN(bb_min.y, pos[i].y);
bb_min.z = MIN(bb_min.z, pos[i].z);
bb_max.x = MAX(bb_max.x, pos[i].x);
bb_max.y = MAX(bb_max.y, pos[i].y);
bb_max.z = MAX(bb_max.z, pos[i].z);
}
// THE BOX AS GEOMETRY, which is Blender's first output and which ours did
// not produce at all. Eight corners, twelve triangles, wound outwards.
Ref<ProcMesh> box;
box.instantiate();
PackedVector3Array verts;
verts.resize(8);
Vector3 *vw = verts.ptrw();
for (int i = 0; i < 8; i++) {
vw[i] = Vector3((i & 1) ? bb_max.x : bb_min.x,
(i & 2) ? bb_max.y : bb_min.y,
(i & 4) ? bb_max.z : bb_min.z);
}
static const int TRIS[36] = {
0, 2, 3, 0, 3, 1, // -Z
4, 5, 7, 4, 7, 6, // +Z
0, 1, 5, 0, 5, 4, // -Y
2, 6, 7, 2, 7, 3, // +Y
0, 4, 6, 0, 6, 2, // -X
1, 3, 7, 1, 7, 5 // +X
};
PackedInt32Array idx;
idx.resize(36);
int *iw = idx.ptrw();
for (int i = 0; i < 36; i++) {
iw[i] = TRIS[i];
}
box->positions = verts;
box->indices = idx;
Ref<ProcGeometry> box_geo;
box_geo.instantiate();
box_geo->set_mesh(box);
p_ctx.write_geometry(p_node_idx, 0, box_geo);
p_ctx.write_vector(p_node_idx, 1, bb_min);
p_ctx.write_vector(p_node_idx, 2, bb_max);
p_ctx.write_vector(p_node_idx, 3, (bb_min + bb_max) * 0.5f);
p_ctx.write_vector(p_node_idx, 4, bb_max - bb_min);
}
// --- SeparateComponentsNode ---
void SeparateComponentsNode::_bind_methods() {
}
SeparateComponentsNode::SeparateComponentsNode() {
// BLENDER'S SIX, IN ORDER. Ours had four and skipped Grease Pencil and
// Volume, so from index 2 on the outputs were displaced: a graph reading
// Point Cloud got our Instances, and the link to Instances addressed a
// socket past the end. "Curve" is Blender's spelling, not "Curves".
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
output_defs.push_back({ "Mesh", SocketType::Geometry, Variant() });
output_defs.push_back({ "Curve", SocketType::Geometry, Variant() });
output_defs.push_back({ "Grease Pencil", SocketType::Geometry, Variant() });
output_defs.push_back({ "Point Cloud", SocketType::Geometry, Variant() });
output_defs.push_back({ "Volume", SocketType::Geometry, Variant() });
output_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
}
void SeparateComponentsNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
auto only = [&geo](auto getter, auto setter) {
Ref<ProcGeometry> out;
out.instantiate();
if (geo.is_valid()) {
auto part = (geo.ptr()->*getter)();
if (part.is_valid()) {
(out.ptr()->*setter)(part);
}
}
return out;
};
p_ctx.write_geometry(p_node_idx, 0,
only(&ProcGeometry::get_mesh, &ProcGeometry::set_mesh));
p_ctx.write_geometry(p_node_idx, 1,
only(&ProcGeometry::get_curves, &ProcGeometry::set_curves));
// GREASE PENCIL has no ProcGeometry component, so this is always empty. It
// exists because Blender's socket 2 is Grease Pencil: without it every
// output after it is one place along, which is the bug this replaces.
{
Ref<ProcGeometry> empty;
empty.instantiate();
p_ctx.write_geometry(p_node_idx, 2, empty);
}
p_ctx.write_geometry(p_node_idx, 3,
only(&ProcGeometry::get_pointcloud, &ProcGeometry::set_pointcloud));
p_ctx.write_geometry(p_node_idx, 4,
only(&ProcGeometry::get_volume, &ProcGeometry::set_volume));
p_ctx.write_geometry(p_node_idx, 5,
only(&ProcGeometry::get_instances, &ProcGeometry::set_instances));
}
// --- SwitchNode ---
void SwitchNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_input_type", "type"), &SwitchNode::set_input_type);
ClassDB::bind_method(D_METHOD("get_input_type_prop"), &SwitchNode::get_input_type_prop);
ADD_PROPERTY(PropertyInfo(Variant::INT, "input_type"), "set_input_type", "get_input_type_prop");
}
SwitchNode::SwitchNode() {
// Blender's socket names, so the bridge resolves them BY NAME rather than
// falling back to the index. "Condition"/"Result" were two more fallbacks.
input_defs.push_back({ "Switch", SocketType::Bool, false });
input_defs.push_back({ "False", SocketType::Geometry, Variant() });
input_defs.push_back({ "True", SocketType::Geometry, Variant() });
output_defs.push_back({ "Output", SocketType::Geometry, Variant() });
}
void SwitchNode::set_input_type(int p_type) {
input_type = (SocketType)p_type;
input_defs.ptrw()[1].type = input_type;
input_defs.ptrw()[2].type = input_type;
output_defs.ptrw()[0].type = input_type;
}
String SwitchNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
// execute()'s scalar path is `cond ? t : f`, and its element-wise path is the
// same choice per element -- which is what a per-vertex shader already is.
// Socket 1 is False, socket 2 is True.
return p_ctx.declare(p_node_idx, 0) + String(" = ") + p_ctx.in(p_node_idx, 0) +
String(" ? ") + p_ctx.in(p_node_idx, 2) + String(" : ") +
p_ctx.in(p_node_idx, 1) + String(";");
}
void SwitchNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
if (input_type == SocketType::Geometry) {
bool condition = p_ctx.read_bool(p_node_idx, 0);
p_ctx.write_geometry(p_node_idx, 0, p_ctx.read_geometry(p_node_idx, condition ? 2 : 1));
return;
}
SocketValue cond = p_ctx.read_input(p_node_idx, 0);
SocketValue f = p_ctx.read_input(p_node_idx, 1);
SocketValue t = p_ctx.read_input(p_node_idx, 2);
if (!cond.is_field && !f.is_field && !t.is_field) {
p_ctx.write_output(p_node_idx, 0, cond.to_bool() ? t : f);
return;
}
// Element-wise, which is Blender's `mode = ELEMENT`: the condition is a
// field over the geometry and the branch is taken per vertex, not once.
int n = MAX(cond.field_size, MAX(f.field_size, t.field_size));
if (n == 0) {
n = 1;
}
AttrDomain domain = cond.is_field ? cond.domain : (f.is_field ? f.domain : t.domain);
switch (input_type) {
case SocketType::Vector: {
PackedVector3Array out;
out.resize(n);
for (int i = 0; i < n; i++) {
out.ptrw()[i] = cond.bool_at(i) ? t.vec_at(i) : f.vec_at(i);
}
p_ctx.write_vec_field(p_node_idx, 0, out, domain);
return;
}
case SocketType::Color: {
PackedColorArray out;
out.resize(n);
for (int i = 0; i < n; i++) {
out.ptrw()[i] = cond.bool_at(i) ? t.color_at(i) : f.color_at(i);
}
p_ctx.write_color_field(p_node_idx, 0, out, domain);
return;
}
case SocketType::Int:
case SocketType::Bool: {
PackedInt32Array out;
out.resize(n);
for (int i = 0; i < n; i++) {
out.ptrw()[i] = cond.bool_at(i) ? t.int_at(i) : f.int_at(i);
}
p_ctx.write_int_field(p_node_idx, 0, out, domain);
return;
}
case SocketType::Rotation: {
PackedVector4Array out;
out.resize(n);
for (int i = 0; i < n; i++) {
Quaternion q = cond.bool_at(i) ? t.quat_at(i) : f.quat_at(i);
out.ptrw()[i] = Vector4(q.x, q.y, q.z, q.w);
}
p_ctx.write_rot_field(p_node_idx, 0, out, domain);
return;
}
default: {
PackedFloat32Array out;
out.resize(n);
for (int i = 0; i < n; i++) {
out.ptrw()[i] = cond.bool_at(i) ? t.float_at(i) : f.float_at(i);
}
p_ctx.write_float_field(p_node_idx, 0, out, domain);
return;
}
}
}
// --- RandomValueNode ---
void RandomValueNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_data_type", "t"), &RandomValueNode::set_data_type);
ClassDB::bind_method(D_METHOD("get_data_type"), &RandomValueNode::get_data_type);
ADD_PROPERTY(PropertyInfo(Variant::INT, "data_type"), "set_data_type", "get_data_type");
}
RandomValueNode::RandomValueNode() {
// Blender's order: Min, Max, ID, SEED. Ours put Seed where ID goes, so a
// graph wiring a seed drove the ID and a graph wiring an ID was dropped --
// and the two are not interchangeable: ID varies per element, seed does not.
input_defs.push_back({ "Min", SocketType::Float, 0.0f });
input_defs.push_back({ "Max", SocketType::Float, 1.0f });
input_defs.push_back({ "ID", SocketType::Int, 0 });
input_defs.push_back({ "Seed", SocketType::Int, 0 });
output_defs.push_back({ "Value", SocketType::Float, Variant() });
}
// Blender's data_type, and the SOCKET SET that goes with it. Defaults measured
// off Blender 5.2 rather than assumed -- INT's Max is 100, not 1, and
// BOOLEAN's Probability is 0.5.
//
// Only four of the fourteen CD_PROP codes are supported by this node; the rest
// cannot be selected in Blender at all, so an unrecognised one leaves the node
// alone rather than inventing a layout.
void RandomValueNode::set_data_type(int p_type) {
Vector<SocketDef> in;
Vector<SocketDef> out;
switch (p_type) {
case 10: // FLOAT
in.push_back({ "Min", SocketType::Float, 0.0f });
in.push_back({ "Max", SocketType::Float, 1.0f });
in.push_back({ "ID", SocketType::Int, 0 });
in.push_back({ "Seed", SocketType::Int, 0 });
out.push_back({ "Value", SocketType::Float, Variant() });
break;
case 11: // INT
in.push_back({ "Min", SocketType::Int, 0 });
in.push_back({ "Max", SocketType::Int, 100 });
in.push_back({ "ID", SocketType::Int, 0 });
in.push_back({ "Seed", SocketType::Int, 0 });
out.push_back({ "Value", SocketType::Int, Variant() });
break;
case 48: // FLOAT_VECTOR
in.push_back({ "Min", SocketType::Vector, Vector3(0, 0, 0) });
in.push_back({ "Max", SocketType::Vector, Vector3(1, 1, 1) });
in.push_back({ "ID", SocketType::Int, 0 });
in.push_back({ "Seed", SocketType::Int, 0 });
out.push_back({ "Value", SocketType::Vector, Variant() });
break;
case 50: // BOOLEAN -- THREE inputs, and ID/Seed shift to 1 and 2.
in.push_back({ "Probability", SocketType::Float, 0.5f });
in.push_back({ "ID", SocketType::Int, 0 });
in.push_back({ "Seed", SocketType::Int, 0 });
out.push_back({ "Value", SocketType::Bool, Variant() });
break;
default:
UtilityFunctions::push_warning(
"RandomValue: Blender supports only FLOAT, INT, FLOAT_VECTOR and "
"BOOLEAN; data_type ", p_type,
" is not one of them and the node is left as it was.");
return;
}
data_type = p_type;
input_defs = in;
output_defs = out;
}
String RandomValueNode::compile_glsl_prelude(int p_node_idx) const {
return String(BLENDER_HASH_GLSL);
}
String RandomValueNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
// Mirrors execute() case for case, including the two things about this
// node that cannot be guessed:
//
// 1. BOOLEAN HAS THREE INPUTS, so ID and Seed sit at 1 and 2 rather
// than 2 and 3.
// 2. THE HASH ARGUMENT ORDER IS NOT THE SAME FOR ALL FOUR TYPES. Float
// and vector hash (seed, id); int and boolean hash (id, seed). Read
// off node_fn_random_value.cc, not inferred -- one order agrees with
// Blender on half the types and looks plausible on the rest.
//
// ⚠ EVERY LITERAL AFTER A NAMED String IS WRAPPED IN String(). A named
// `const String` plus a bare "literal" is an AMBIGUOUS overload in
// godot-cpp -- the char array converts equally well to String and to
// StringName -- while the prvalue `p_ctx.declare(...) + " = "` that every
// other node in this tree happens to use does not. Same expression, and
// the only difference is whether the left side was given a name.
const bool is_bool = (data_type == 50);
const String id = "uint(" + p_ctx.in(p_node_idx, is_bool ? 1 : 2) + ")";
const String seed = "uint(" + p_ctx.in(p_node_idx, is_bool ? 2 : 3) + ")";
const String a = "(" + p_ctx.in(p_node_idx, 0) + ")";
const String decl = p_ctx.declare(p_node_idx, 0);
switch (data_type) {
case 10: { // FLOAT
const String b = "(" + p_ctx.in(p_node_idx, 1) + ")";
return decl + String(" = bn_hash_to_float(") + seed + String(", ") + id +
String(") * (") + b + String(" - ") + a + String(") + ") + a + String(";");
}
case 48: { // FLOAT_VECTOR -- three hashes, one per component.
const String b = "(" + p_ctx.in(p_node_idx, 1) + ")";
String e = "vec3(";
const char *c[3] = { "x", "y", "z" };
for (int i = 0; i < 3; i++) {
if (i > 0) {
e += ", ";
}
const String comp = String(".") + c[i];
e += String("bn_hash_to_float(") + seed + String(", ") + id + String(", ") +
itos(i) + String("u) * (") + b + comp + String(" - ") + a + comp +
String(") + ") + a + comp;
}
return decl + String(" = ") + e + String(");");
}
case 11: { // INT
const String b = "(" + p_ctx.in(p_node_idx, 1) + ")";
const String pre = String("rv") + itos(p_node_idx);
// UNSIGNED throughout, so the whole 32-bit span fits and the range
// wraps to 0 for INT_MIN..INT_MAX, where a modulo would divide by
// zero. Blender SWAPS rather than erroring when Min > Max.
return String("int ") + pre + String("_mn = min(") + a + String(", ") + b +
String(");\n\tint ") + pre + String("_mx = max(") + a + String(", ") + b +
String(");\n\tuint ") + pre + String("_h = bn_hash(") + id + String(", ") +
seed + String(");\n\tuint ") + pre + String("_r = uint(") + pre +
String("_mx) - uint(") + pre + String("_mn) + 1u;\n\t") + decl +
String(" = int(uint(") + pre + String("_mn) + ((") + pre +
String("_r == 0u) ? ") + pre + String("_h : (") + pre + String("_h % ") +
pre + String("_r)));");
}
case 50: // BOOLEAN
// `<=`, not `<`, so a probability of exactly 0 can still be true
// when the hash is exactly 0.
return decl + String(" = bn_hash_to_float(") + id + String(", ") + seed +
String(") <= ") + a + String(";");
default:
// set_data_type refuses anything else, so this is unreachable
// rather than a silent fallback.
return String();
}
}
void RandomValueNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
// BOOLEAN HAS THREE INPUTS, so ID and Seed are not where they are for the
// other three. Retyping the sockets without moving them would have ID
// reading the seed's link and Seed reading nothing.
const bool is_bool = (data_type == 50);
const int id_sock = is_bool ? 1 : 2;
const int seed_sock = is_bool ? 2 : 3;
SocketValue a = p_ctx.read_input(p_node_idx, 0); // Min, or Probability
SocketValue b = is_bool ? SocketValue() : p_ctx.read_input(p_node_idx, 1); // Max
SocketValue idv = p_ctx.read_input(p_node_idx, id_sock);
SocketValue seedv = p_ctx.read_input(p_node_idx, seed_sock);
const bool is_field = a.is_field || b.is_field || idv.is_field || seedv.is_field;
int n = MAX(MAX(a.field_size, b.field_size), MAX(idv.field_size, seedv.field_size));
if (n == 0) {
n = 1;
}
const AttrDomain domain = a.is_field ? a.domain
: (idv.is_field ? idv.domain
: (seedv.is_field ? seedv.domain : b.domain));
// THE ARGUMENT ORDER IS NOT THE SAME FOR ALL FOUR TYPES. Float and vector
// hash (seed, id); int and boolean hash (id, seed). Read off
// node_fn_random_value.cc, not inferred -- one order would agree with
// Blender on half the types and look plausible on the rest.
auto float_at = [&](int e) -> float {
const uint32_t id = (uint32_t)idv.int_at(e);
const uint32_t seed = (uint32_t)seedv.int_at(e);
const float t = bnoise::hash_to_float(seed, id);
const float mn = a.float_at(e);
const float mx = b.float_at(e);
return t * (mx - mn) + mn;
};
auto vec_at = [&](int e) -> Vector3 {
const uint32_t id = (uint32_t)idv.int_at(e);
const uint32_t seed = (uint32_t)seedv.int_at(e);
const Vector3 mn = a.vec_at(e);
const Vector3 mx = b.vec_at(e);
return Vector3(bnoise::hash_to_float(seed, id, 0) * (mx.x - mn.x) + mn.x,
bnoise::hash_to_float(seed, id, 1) * (mx.y - mn.y) + mn.y,
bnoise::hash_to_float(seed, id, 2) * (mx.z - mn.z) + mn.z);
};
auto int_at = [&](int e) -> int32_t {
int32_t mn = a.int_at(e);
int32_t mx = b.int_at(e);
if (mn > mx) {
SWAP(mn, mx);
}
const uint32_t h = bnoise::hash((uint32_t)idv.int_at(e), (uint32_t)seedv.int_at(e));
// UNSIGNED, so the whole 32-bit span fits. The range wraps to 0 for
// INT_MIN..INT_MAX, where a modulo would divide by zero.
const uint32_t range = (uint32_t)mx - (uint32_t)mn + 1u;
const uint32_t modulo_result = (range == 0u) ? h : (h % range);
return (int32_t)((uint32_t)mn + modulo_result);
};
auto bool_at = [&](int e) -> bool {
// `<=`, not `<`, so a probability of exactly 0 can still be true when
// the hash is exactly 0.
return bnoise::hash_to_float((uint32_t)idv.int_at(e), (uint32_t)seedv.int_at(e)) <=
a.float_at(e);
};
if (!is_field) {
switch (data_type) {
case 11: p_ctx.write_int(p_node_idx, 0, int_at(0)); break;
case 48: p_ctx.write_vector(p_node_idx, 0, vec_at(0)); break;
case 50: p_ctx.write_bool(p_node_idx, 0, bool_at(0)); break;
default: p_ctx.write_float(p_node_idx, 0, float_at(0)); break;
}
return;
}
// ID IS THE FIELD INPUT THAT MATTERS. Ours ignored it entirely and hashed
// the seed alone, so every element of a field got the same number -- which
// is the one thing this node exists not to do.
switch (data_type) {
case 11: {
PackedInt32Array out;
out.resize(n);
for (int e = 0; e < n; e++) {
out.ptrw()[e] = int_at(e);
}
p_ctx.write_int_field(p_node_idx, 0, out, domain);
} break;
case 48: {
PackedVector3Array out;
out.resize(n);
for (int e = 0; e < n; e++) {
out.ptrw()[e] = vec_at(e);
}
p_ctx.write_vec_field(p_node_idx, 0, out, domain);
} break;
case 50: {
PackedInt32Array out;
out.resize(n);
for (int e = 0; e < n; e++) {
out.ptrw()[e] = bool_at(e) ? 1 : 0;
}
p_ctx.write_int_field(p_node_idx, 0, out, domain);
} break;
default: {
PackedFloat32Array out;
out.resize(n);
for (int e = 0; e < n; e++) {
out.ptrw()[e] = float_at(e);
}
p_ctx.write_float_field(p_node_idx, 0, out, domain);
} break;
}
}
} // namespace godot