OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / scene / proc_geometry_node.h

141 lines · c ref:
#ifndef PROC_NODES_PROC_GEOMETRY_NODE_H
#define PROC_NODES_PROC_GEOMETRY_NODE_H

#include "../graph/graph_executor.h"
#include "../graph/proc_node_graph.h"
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/classes/multi_mesh_instance3d.hpp>
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/rendering_device.hpp>

namespace godot {

// Scene node that hosts and evaluates a proc-nodes graph
class ProcGeometryNode : public Node3D {
	GDCLASS(ProcGeometryNode, Node3D);

protected:
	static void _bind_methods();
	void _notification(int p_what);

	Ref<ProcNodeGraph> graph;

	// The mesh handed to the graph's Group Input. A Blender geometry tree acts
	// on the object's own mesh; without this a bridged graph has no geometry to
	// act on and bakes to nothing.
	Ref<Mesh> input_mesh;

	// The same input, as a MESH rather than as a draw buffer: points, the
	// corner array, faces as corner ranges, edges and attributes on their
	// domains. blender-bridge fills this; when it is present it WINS over
	// input_mesh, because input_mesh can only ever be the triangulated,
	// corner-expanded shadow of it. See ProcMesh::from_mesh_data.
	Dictionary input_mesh_data;
	bool auto_update = true;
	bool live_evaluation = true;
	bool use_gpu = true;
	bool needs_update = true;

	// Cached execution plan
	ExecutionPlan cached_plan;
	bool plan_dirty = true;

	// Simulation zone frame cache: zone_id -> geometry from previous frame
	// Persists across evaluations so simulation state accumulates over time
	HashMap<int, Ref<ProcGeometry>> simulation_cache;

	// Persistent GPU resources (local RD + compiled pipelines survive across evaluations)
	RenderingDevice *gpu_rd = nullptr;
	void _ensure_gpu_device();
	void _destroy_gpu_device();

	// Baked output
	Ref<ArrayMesh> baked_mesh;
	Ref<MultiMesh> baked_multimesh;

	// Cached display mesh (reused across evaluations when topology unchanged)
	Ref<ArrayMesh> cached_display_mesh;
	int cached_vert_count = 0;
	int cached_index_count = 0;

	// Child nodes for rendering
	MeshInstance3D *mesh_output = nullptr;
	MultiMeshInstance3D *multimesh_output = nullptr;

	void _ensure_children();
	void _evaluate();
	Dictionary _build_overrides() const;
	void _update_display();

public:
	// Graph
	void set_graph(const Ref<ProcNodeGraph> &p_graph);
	Ref<ProcNodeGraph> get_graph() const { return graph; }

	// Settings
	void set_auto_update(bool p_auto) { auto_update = p_auto; }
	bool get_auto_update() const { return auto_update; }

	void set_live_evaluation(bool p_live) { live_evaluation = p_live; }
	bool get_live_evaluation() const { return live_evaluation; }

	void set_use_gpu(bool p_gpu);

	bool get_use_gpu() const { return use_gpu; }

	// Manual evaluation
	void set_input_mesh(const Ref<Mesh> &p_mesh) { input_mesh = p_mesh; mark_dirty(); }
	Ref<Mesh> get_input_mesh() const { return input_mesh; }

	void set_input_mesh_data(const Dictionary &p_data) { input_mesh_data = p_data; mark_dirty(); }
	Dictionary get_input_mesh_data() const { return input_mesh_data; }

	// The evaluated result as a mesh, for the Godot -> Blender direction. Every
	// face, edge and corner value survives, which bake_mesh() cannot carry.
	Dictionary get_output_mesh_data();

	void evaluate();

	// Bake: evaluate once and save result
	Ref<ArrayMesh> bake_mesh();

	// Same evaluation, returning the ProcGeometry itself. bake_mesh() converts
	// to an ArrayMesh, which is per-VERTEX only, so every corner-, face- and
	// edge-domain value is lost on the way out. INSTRUMENT ONLY.
	Ref<ProcGeometry> bake_geometry();

	// Import graph from JSON and set it (used by blender-bridge UDS sync)
	bool set_graph_from_json(const String &p_json);

	// Mark for re-evaluation (emits defaults_changed for editor UI sync)
	void mark_dirty();

	// Mark plan as dirty (graph structure changed — recompile execution plan)
	void invalidate_plan() { plan_dirty = true; needs_update = true; }

	// node type -> "gpu" | "cpu..." for the last evaluation, from
	// GraphExecutor::last_exec_report.
	static Dictionary get_last_exec_report();

	// Called when graph resource emits "changed" (e.g. node property edited in inspector)
	void _on_graph_changed();

	// ── Param delta queue (for blender-bridge Godot→Blender sync) ────
	// Editor pushes deltas here; bridge drains them each frame.
	// No signals between extensions — queue dies safely with the node.
private:
	Vector<Dictionary> _param_delta_queue;
	bool _suppress_param_deltas = false;
public:
	void push_param_delta(int p_node_idx, int p_socket_idx, const Variant &p_value);
	TypedArray<Dictionary> drain_param_deltas();
	void suppress_param_deltas(bool p_suppress) { _suppress_param_deltas = p_suppress; }

	ProcGeometryNode() = default;
	~ProcGeometryNode();
};

} // namespace godot

#endif // PROC_NODES_PROC_GEOMETRY_NODE_H