OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / nodes / volume_nodes.h

118 lines · c ref:
#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