#ifndef PROC_NODES_PROC_MESH_H
#define PROC_NODES_PROC_MESH_H
#include "attribute_storage.h"
#include <godot_cpp/classes/array_mesh.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/packed_color_array.hpp>
#include <godot_cpp/variant/packed_float32_array.hpp>
#include <godot_cpp/variant/packed_int32_array.hpp>
#include <godot_cpp/variant/packed_vector2_array.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/packed_vector3_array.hpp>
namespace godot {
class ProcMesh : public RefCounted {
GDCLASS(ProcMesh, RefCounted);
protected:
static void _bind_methods();
public:
// Built-in arrays
PackedVector3Array positions;
PackedVector3Array normals;
PackedFloat32Array tangents; // x,y,z,sign per corner
PackedVector2Array uvs;
PackedColorArray colors;
PackedInt32Array indices; // Triangle indices
// Build from an engine mesh, so geometry authored outside a graph -- an
// object's own mesh, a bridged cage -- can be fed INTO one. The reverse,
// to_array_mesh, has always existed; this direction had not, which is why
// nothing could supply a graph's Group Input.
static Ref<ProcMesh> from_array_mesh(const Ref<Mesh> &p_mesh);
// THE MESH CHANNEL. A mesh as both authoring systems define one -- points,
// a corner array, faces as corner ranges, edges, and named attributes on
// their domains -- carried in a Dictionary so blender-bridge can hand one
// over through ClassDB reflection without a compile-time dependency.
//
// ⛔ NOT an ArrayMesh. An ArrayMesh has ONE domain, the vertex, plus an
// index buffer: a face, corner or edge value has nowhere to live in it, and
// a corner normal can only be expressed by duplicating the vertex. Every
// mesh that crossed the bridge as an ArrayMesh arrived triangulated and
// de-indexed -- the mountain's 10088-vertex quad cage as 60516 loose
// corners -- and no graph could ask it a topological question.
//
// points PackedVector3Array positions, sender's frame
// corners PackedInt32Array vertex index per corner
// faces PackedInt32Array face_count + 1 offsets into corners
// edges PackedInt32Array 2 per edge (the SENDER's order)
// attributes Array[Dictionary] {name, domain, type, data}
//
// See blender-bridge/docs/mesh-channel-design.md.
static Ref<ProcMesh> from_mesh_data(const Dictionary &p_data);
Dictionary to_mesh_data() const;
PackedInt32Array face_offsets; // Face → corner range (OffsetIndices)
// Named attributes per domain
AttributeStorage point_attrs;
AttributeStorage corner_attrs;
AttributeStorage face_attrs;
AttributeStorage edge_attrs;
// Domain sizes
int vertex_count() const { return positions.size(); }
int triangle_count() const { return indices.size() / 3; }
int face_count() const;
int corner_count() const;
// THE EDGE DOMAIN. ProcMesh stores positions, triangles and face offsets;
// it has never stored edges, so `domain_size(Edge)` returned 0 with a TODO
// and the whole edge domain was unreachable -- Split Edges was a
// pass-through, Merge By Distance had no Connected mode, and a per-edge
// attribute had nowhere to live.
//
// Edges are DERIVED from the triangles rather than stored, and derived
// DETERMINISTICALLY -- each undirected pair normalised low-to-high and the
// set sorted -- so the same mesh always yields the same edge ORDER and a
// per-edge attribute means the same thing twice. Not cached: a cache would
// need invalidating on every topology change, and there is no single place
// those go through. Correctness first; it can be cached when it is hot.
//
// The order is OURS, not Blender's: Blender builds an icosphere's edges in
// its own sequence, so the COUNT agrees and index i need not be the same
// edge. Said here so nobody asserts the stronger thing.
PackedInt32Array compute_edges() const; // 2 ints per edge, v0 < v1
int edge_count() const;
// NGONS. `indices` is the CORNER array and `face_offsets` groups it into
// faces -- offsets[f] .. offsets[f+1] are face f's corners, so it has
// face_count + 1 entries. EMPTY face_offsets still means "every face is a
// triangle", which is what every existing mesh here is, so nothing changes
// meaning by adding this.
//
// It matters because Blender keeps QUADS and ngons: a 2x2 grid is one face
// there and two triangles here, so face, corner and edge counts diverge,
// a per-face value has nowhere to agree, and the subdivided mountain's
// vertices land in slightly different places.
bool has_ngons() const { return face_offsets.size() > 1; }
int face_corner_begin(int p_face) const {
return has_ngons() ? face_offsets[p_face] : p_face * 3;
}
int face_size(int p_face) const {
return has_ngons() ? (face_offsets[p_face + 1] - face_offsets[p_face]) : 3;
}
// Faces fanned into triangles, for anything that must have them -- the
// renderer, and the GPU path. A quad becomes two triangles about corner 0,
// which is what Blender's own triangulation does for a convex quad.
PackedInt32Array triangulated_indices() const;
int domain_size(AttrDomain p_domain) const;
// Convert to Godot ArrayMesh
Ref<ArrayMesh> to_array_mesh() const;
// Update an existing ArrayMesh in place (avoids full resource recreation).
// Returns false if topology changed (caller should use to_array_mesh instead).
// The existing mesh must have exactly one surface with matching vertex/index counts.
bool update_array_mesh(Ref<ArrayMesh> p_mesh) const;
// Reverse every face's corner order, permuting corner-domain values with
// it. A face is a list of corners and that order is the only thing that
// says which side is the front -- Blender and Godot read it oppositely,
// so a mesh authored in one has to be turned round to enter the other.
void reverse_faces();
// Recalculate normals from the FACES, in Godot's winding.
void recalculate_normals();
// The one implementation both that and to_array_mesh() use.
void compute_normals(PackedVector3Array &r_out) const;
// Pack positions + normals (+ UVs if present) into Godot's internal vertex
// buffer format for use with surface_update_vertex_region().
// p_stride / p_pos_offset / p_normal_offset / p_uv_offset come from
// RenderingServer::mesh_surface_get_format_vertex_stride/offset queries.
// Offsets set to -1 mean "attribute not present".
PackedByteArray pack_vertex_buffer(uint32_t p_stride,
uint32_t p_pos_offset, uint32_t p_normal_offset,
int32_t p_uv_offset) const;
// Deep copy
Ref<ProcMesh> duplicate_mesh() const;
// INSTRUMENT ONLY. to_array_mesh() is per-VERTEX, so a corner-, face- or
// edge-domain value cannot leave this class through it -- which is why the
// UV Map outputs could not be compared against Blender at all. These hand
// the raw domain to GDScript so a probe can dump it.
PackedInt32Array get_indices() const { return indices; }
PackedInt32Array get_face_offsets() const { return face_offsets; }
int get_corner_count() const { return corner_count(); }
int get_face_count() const { return face_count(); }
int get_domain_size(int p_domain) const { return domain_size(static_cast<AttrDomain>(p_domain)); }
// p_domain is AttrDomain: 0 Point, 1 Edge, 2 Face, 3 Corner.
Array get_attribute_values(int p_domain, const StringName &p_name) const;
PackedStringArray get_attribute_names(int p_domain) const;
ProcMesh() = default;
};
} // namespace godot
#endif // PROC_NODES_PROC_MESH_H