OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge / src / blender_bridge.h

157 lines · c ref:
#pragma once

#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/mesh.hpp>
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/classes/image.hpp>
#include <godot_cpp/classes/image_texture.hpp>
#include <godot_cpp/classes/standard_material3d.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/packed_float32_array.hpp>
#include <godot_cpp/variant/packed_vector2_array.hpp>
#include <godot_cpp/variant/packed_vector3_array.hpp>
#include <godot_cpp/variant/string.hpp>
#include <godot_cpp/variant/transform3d.hpp>
#include <godot_cpp/variant/typed_array.hpp>

#include <set>

#include "shm_protocol.h"
#include "uds_server.h"

using namespace godot;

class BlenderBridge : public Node {
	GDCLASS(BlenderBridge, Node)

protected:
	static void _bind_methods();

public:
	BlenderBridge();
	~BlenderBridge();

	// ── Server lifecycle ────────────────────────────────────────────────
	Error start_server(const String &socket_path = "/tmp/blender_bridge.sock");
	void stop_server();
	bool is_connected() const;
	int get_client_count() const;

	// ── Bootstrap: export current Godot scene to shared memory ──────────
	Error export_scene(Node *root);
	Dictionary get_export_info() const;

	// ── Live sync: import changes from shared memory ────────────────────
	void import_changes(Node *root);

	// ── Frame tick ──────────────────────────────────────────────────────
	void _process(double delta) override;

	// ── Properties ──────────────────────────────────────────────────────
	void set_shm_name(const String &name);
	String get_shm_name() const;
	void set_shm_size(int size);
	int get_shm_size() const;
	void set_max_objects(int count);
	int get_max_objects() const;
	void set_auto_sync(bool enabled);
	bool get_auto_sync() const;

	// ── VirtualTexture integration ──────────────────────────────────────
	void set_virtual_texture_path(const NodePath &path);
	NodePath get_virtual_texture_path() const;

	// Forward texture dirty rect from Blender to VirtualTexture
	void notify_texture_dirty(int texture_id, int tile_x, int tile_y, int w, int h, int mip);

	// ── Finalize (save textures + scene to disk) ────────────────────────
	Error finalize_scene(Node *root, const String &save_dir);

	// ── Signals ─────────────────────────────────────────────────────────
	// client_connected(client_id: int)
	// client_disconnected(client_id: int)
	// scene_exported(object_count: int)
	// changes_imported(object_count: int)
	// texture_dirty(texture_id: int, tile_x: int, tile_y: int, w: int, h: int, mip: int)

private:
	// ── SHM + UDS ───────────────────────────────────────────────────────
	ShmRegion _shm;
	UdsServer _uds;
	String _shm_name = "/blender_bridge";
	int _shm_size = 24 * 1024 * 1024; // 24 MB (8 MB region 0 + 8 MB region 1 + 8 MB tile ring)
	int _max_objects = 256;
	bool _auto_sync = false;
	bool _server_running = false;
	bool _import_pending = false; // Set by UDS "updated" from Blender
	int _import_cooldown = 0;    // Frames to skip _detect_and_push_changes after import
	NodePath _virtual_texture_path;

	// ── Image texture cache (whole-image transfer from Blender) ──────────
	HashMap<int, Ref<ImageTexture>> _image_textures;

	// ── UDS message handling ────────────────────────────────────────────
	void _handle_uds_message(const UdsMessage &msg);

	// ── Split-region SHM pointers ───────────────────────────────────────
	// Region 0: Godot→Blender (Godot writes, Blender reads)
	// Region 1: Blender→Godot (Blender writes, Godot reads)
	void *_write_base = nullptr; // Region 0 base
	void *_read_base = nullptr;  // Region 1 base
	size_t _region_size = 0;

	// ── Sequence tracking ───────────────────────────────────────────────
	uint64_t _export_sequence = 0;
	uint64_t _last_import_sequence = 0;

	// ── Object ID mapping ───────────────────────────────────────────────
	struct TrackedObject {
		uint32_t object_id;
		ObjectID godot_instance_id;
		String name;
		ObjectType type;
		// Change detection: cached transform hash
		uint64_t transform_hash = 0;
		// Origin tracking: true if created by Blender import
		bool from_blender = false;
	};
	std::vector<TrackedObject> _tracked_objects;
	uint32_t _next_object_id = 1;

	// ── Scene structure tracking ────────────────────────────────────────
	ObjectID _export_root_id;
	int _last_export_node_count = 0;
	std::set<uint64_t> _known_instance_ids;

	// ── Export helpers ───────────────────────────────────────────────────
	void _collect_exportable_nodes(Node *node, std::vector<Node3D *> &out);
	uint32_t _export_node(Node3D *node, uint32_t index, uint8_t *blob_base, uint32_t &blob_offset);
	static void _write_name_prefix(const String &name, uint8_t *blob_base, uint32_t &blob_offset);

	// ── Change detection ────────────────────────────────────────────────
	static uint64_t _hash_transform(const Transform3D &xform);
	void _detect_and_push_changes();

	// ── Coordinate conversion ───────────────────────────────────────────
	// Godot Y-up (x, y, z) → Blender Z-up (x, -z, y)
	static void _godot_to_blender_vec3(const float *in, float *out) {
		out[0] = in[0];
		out[1] = -in[2];
		out[2] = in[1];
	}

	// Blender Z-up (x, y, z) → Godot Y-up (x, z, -y)
	static void _blender_to_godot_vec3(const float *in, float *out) {
		out[0] = in[0];
		out[1] = in[2];
		out[2] = -in[1];
	}

	// Transform3D → 4×4 float array in Blender convention
	static void _export_transform(const Transform3D &xform, float *out);
};