#ifndef PROC_NODES_VOLUME_NODES_H
#define PROC_NODES_VOLUME_NODES_H
#include "../graph/proc_node.h"
namespace godot {
// SphereSDF — outputs density: radius - length(pos - center)
// Positive inside sphere, negative outside. GPU-capable.
class SphereSdfNode : public ProcNode {
GDCLASS(SphereSdfNode, ProcNode);
float radius = 1.0f;
protected:
static void _bind_methods();
public:
SphereSdfNode();
StringName get_node_type() const override { return "SphereSDF"; }
String get_node_title() const override { return "Sphere SDF"; }
String get_node_category() const override { return "Volume"; }
void set_radius(float p_r) { radius = p_r; }
float get_radius() const { return radius; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
};
// VolumeBooleanNode — union, intersect, or subtract two density fields.
// GPU-capable.
class VolumeBooleanNode : public ProcNode {
GDCLASS(VolumeBooleanNode, ProcNode);
public:
enum BooleanOp {
OP_UNION = 0, // max(a, b)
OP_INTERSECT = 1, // min(a, b)
OP_SUBTRACT = 2, // min(a, -b)
};
private:
BooleanOp operation = OP_UNION;
protected:
static void _bind_methods();
public:
VolumeBooleanNode();
StringName get_node_type() const override { return "VolumeBoolean"; }
String get_node_title() const override { return "Volume Boolean"; }
String get_node_category() const override { return "Volume"; }
void set_operation(int p_op) { operation = (BooleanOp)p_op; }
int get_operation() const { return (int)operation; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
};
// VolumeToMeshNode — terminal node that compiles an SDF chain into a ProcVolume.
// Reads the density float from its input and packages the compiled GLSL into
// a ProcVolume stored on the output ProcGeometry.
class VolumeToMeshNode : public ProcNode {
GDCLASS(VolumeToMeshNode, ProcNode);
protected:
static void _bind_methods();
public:
VolumeToMeshNode();
// ⚠ RE-ENTRANCY. Meshing evaluates the graph's density at every grid corner,
// and the only way to get a density AT A POSITION is to run the graph --
// which runs THIS NODE again. The nested pass must not mesh, or it recurses
// until the stack ends. It writes a bare geometry and returns.
bool meshing_ = false;
StringName get_node_type() const override { return "VolumeToMesh"; }
String get_node_title() const override { return "Volume to Mesh"; }
String get_node_category() const override { return "Volume"; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
// ⛔ THIS NODE NAMES THE DENSITY. It does not compute one.
//
// It emits nothing, so supports_gpu() stays false -- but it is
// STRUCTURALLY TRANSPARENT, the same category as Group Input and Group
// Output: it carries the graph's boundary, not an operation. Without that
// distinction `!supports_gpu()` is a REFUSAL (proc_node_graph.cpp), so a
// graph ending in this node could not be compiled to GLSL AT ALL -- which
// is the "volume node GLSL connectivity" the dc-terrain-nubis notes list as
// missing, and the reason nothing has ever built a ProcVolume.
//
// Its Density input is the graph saying what its density IS, exactly as a
// Group Output float does, and find_density_output() reads both. So an
// authored volume -- a cloud formation, a fog body, an SDF blob -- compiles
// to `float proc_density(vec3 pos)` and rides in the ProcVolume that
// execute() now fills, for a GPU consumer (a FogVolume fog shader, or
// dc-terrain) to raymarch. It is not meshed, despite the name.
bool supports_gpu() const override { return false; }
bool gpu_transparent() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
};
} // namespace godot
VARIANT_ENUM_CAST(godot::VolumeBooleanNode::BooleanOp);
#endif // PROC_NODES_VOLUME_NODES_H