#ifndef DC_SEAM_H
#define DC_SEAM_H
// Gildea seam stitching for octree clipmap DC terrain.
// Builds temporary octrees from boundary nodes of adjacent cells and
// contours them to fill gaps between LOD levels.
//
// Ported from ngildea/leven (clipmap.cpp + octree.cpp).
// Pure C++ -- no Godot dependencies.
#include "dc_octree_clipmap.h"
#include "dc_contour.h" // DCMeshData
#include <vector>
#include <cstdint>
// ---------- SeamOctreeNode ----------
// Lightweight node for the temporary seam octree.
// Allocated from a SeamOctreeArena and discarded after mesh generation.
struct SeamOctreeNode {
enum Type : uint8_t { NONE, INTERNAL, LEAF };
Type type = NONE;
int min[3] = {};
int size = 0;
SeamOctreeNode* children[8] = {};
// Leaf data (only valid when type == LEAF)
int vertex_index = -1;
float position[3] = {};
float normal[3] = {};
uint32_t corner_signs = 0; // 8-bit corner sign field
};
// ---------- SeamOctreeArena ----------
// Simple arena allocator for seam octree nodes.
// All nodes are freed together when the arena is cleared.
class SeamOctreeArena {
static constexpr int BLOCK_SIZE = 256;
struct Block {
SeamOctreeNode nodes[BLOCK_SIZE];
};
std::vector<Block*> blocks_;
int next_ = BLOCK_SIZE;
public:
SeamOctreeArena() = default;
~SeamOctreeArena() { clear(); }
SeamOctreeArena(const SeamOctreeArena&) = delete;
SeamOctreeArena& operator=(const SeamOctreeArena&) = delete;
SeamOctreeNode* alloc() {
if (next_ >= BLOCK_SIZE) {
blocks_.push_back(new Block());
next_ = 0;
}
SeamOctreeNode* n = &blocks_.back()->nodes[next_++];
*n = SeamOctreeNode{};
return n;
}
void clear() {
for (auto* b : blocks_) delete b;
blocks_.clear();
next_ = BLOCK_SIZE;
}
};
// ---------- Seam Stitching API ----------
// Filter: does a seam node at [node_min, node_max) belong on the boundary
// between a host cell and its neighbor at octant child_index?
// seam_bounds = host_min + host_size (the shared face/edge/corner).
bool seam_filter_node(int child_index,
const int seam_bounds[3],
const int node_min[3],
const int node_max[3]);
// Select seam nodes from a neighbor that belong in the host cell's seam mesh.
// host_min: world-space min of the host cell
// host_size: host cell size in voxel units
// neighbor_size: neighbor cell size in voxel units
// neighbor_index: which of 8 octant directions the neighbor is in
// seam_nodes / num_seam_nodes: the neighbor's boundary node array
// voxels_per_cell: grid resolution (e.g. 32)
// out: appended with pointers to selected SeamOctreeNodes (caller-allocated)
void seam_select_nodes(
const int host_min[3],
int host_size,
int neighbor_size,
int neighbor_index,
const SeamNodeInfo* seam_nodes,
int num_seam_nodes,
int voxels_per_cell,
SeamOctreeArena& arena,
std::vector<SeamOctreeNode*>& out);
// Build a temporary octree bottom-up from leaf nodes of potentially
// different sizes. Returns the root node (owned by arena).
SeamOctreeNode* seam_construct_upwards(
SeamOctreeArena& arena,
const std::vector<SeamOctreeNode*>& input_nodes,
const int root_min[3],
int root_size);
// Generate a seam mesh from a temporary seam octree root.
// Uses the standard DC cellProc/faceProc/edgeProc algorithm.
// host_min: used to filter triangles that span multiple cells.
void seam_generate_mesh(
SeamOctreeNode* root,
const int host_min[3],
int host_size,
DCMeshData& out_mesh);
// High-level: generate a complete seam mesh for a cell by collecting
// seam nodes from its neighbors and contouring them.
// neighbors[8]: pointers to the 8 adjacent cells (null if no neighbor).
// neighbors[i] is the cell at host_min + CHILD_MIN_OFFSETS[i] * host_size.
// Each neighbor may itself have sub-cells (different LOD) — pass active leaves.
struct SeamNeighborInfo {
const SeamNodeInfo* seam_nodes = nullptr;
int num_seam_nodes = 0;
int cell_size = 0; // in voxel units
};
void seam_generate_for_cell(
const int host_min[3],
int host_size,
int voxels_per_cell,
const std::vector<SeamNeighborInfo>& neighbors, // indexed by octant [0..7]
DCMeshData& out_mesh);
#endif // DC_SEAM_H