#ifndef DC_TERRAIN_H
#define DC_TERRAIN_H
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/array_mesh.hpp>
// MeshInstance3D only used for debug seam visualization
#include <godot_cpp/classes/mesh_instance3d.hpp>
#include <godot_cpp/classes/immediate_mesh.hpp>
#include <godot_cpp/classes/standard_material3d.hpp>
#include <godot_cpp/classes/shader_material.hpp>
#include <godot_cpp/classes/shader.hpp>
#include <godot_cpp/classes/texture2d_array.hpp>
#include <godot_cpp/classes/texture2d.hpp>
#include <godot_cpp/classes/rendering_server.hpp>
#include <godot_cpp/classes/rendering_device.hpp>
#include <godot_cpp/classes/physics_server3d.hpp>
#include <godot_cpp/variant/color.hpp>
#include <godot_cpp/variant/rid.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include "dc_density.h"
#include "dc_density_grid.h"
#include "dc_contour.h"
#include "dc_generator.h"
#include "dc_generator_noise.h"
#include "dc_terrain_generator.h"
#include "dc_octree_clipmap.h"
#include "dc_seam.h"
#include "dc_worker_cpu.h"
#include "dc_collision_trimesh.h"
#include "dc_collision_sdf.h"
// Removed: dc_vulkan_ext.h, dc_clas_builder.h, dc_lod_dag.h (GPU RT path — re-add later)
// Plane was used for manual frustum culling (removed — Godot handles it via RS)
#include <thread>
#include <atomic>
#include <mutex>
#include <vector>
#include <chrono>
#include <deque>
#include <memory>
#include <functional>
#include <unordered_map>
#include <unordered_set>
namespace godot {
class DCTerrain : public Node3D {
GDCLASS(DCTerrain, Node3D)
protected:
static void _bind_methods();
public:
DCTerrain();
~DCTerrain();
void _ready() override;
void _process(double delta);
void _notification(int p_what);
void rebuild();
void warmup_shaders();
void _on_generator_changed();
// -- DensityConfig properties --
void set_seed(int p_seed);
int get_seed() const;
void set_primary_frequency(float p_val);
float get_primary_frequency() const;
void set_primary_strength(float p_val);
float get_primary_strength() const;
void set_tunnel_frequency(float p_val);
float get_tunnel_frequency() const;
void set_tunnel_strength(float p_val);
float get_tunnel_strength() const;
void set_tunnel_radius(float p_val);
float get_tunnel_radius() const;
void set_ridge_frequency(float p_val);
float get_ridge_frequency() const;
void set_ridge_strength(float p_val);
float get_ridge_strength() const;
void set_warp_frequency(float p_val);
float get_warp_frequency() const;
void set_warp_strength(float p_val);
float get_warp_strength() const;
void set_detail_frequency(float p_val);
float get_detail_frequency() const;
void set_detail_strength(float p_val);
float get_detail_strength() const;
void set_ground_bias(float p_val);
float get_ground_bias() const;
void set_solid_floor_depth(float p_val);
float get_solid_floor_depth() const;
void set_sky_height(float p_val);
float get_sky_height() const;
void set_flat_center_radius(float p_val);
float get_flat_center_radius() const;
void set_voronoi_cell_count(int p_val);
int get_voronoi_cell_count() const;
void set_floor_strength(float p_val);
float get_floor_strength() const;
void set_floor_height_min(float p_val);
float get_floor_height_min() const;
void set_floor_height_max(float p_val);
float get_floor_height_max() const;
void set_room_height(float p_val);
float get_room_height() const;
void set_terrain_size(float p_val);
float get_terrain_size() const;
// -- Terrain-specific properties --
void set_terrain_color(const Color &p_color);
Color get_terrain_color() const;
void set_custom_material(const Ref<Material> &p_material);
Ref<Material> get_custom_material() const;
void set_voxel_size(float p_val);
float get_voxel_size() const;
void set_octree_depth(int p_val);
int get_octree_depth() const;
void set_seamless_lod(bool p_val);
bool get_seamless_lod() const;
void set_use_adaptive_qef(bool p_val);
bool get_use_adaptive_qef() const;
void set_texture_atlas(const Ref<Texture2DArray> &p_atlas); // WAD atlas, layer == material_id
void set_glow_atlas(const Ref<Texture2DArray> &p_atlas); // PBR emissive glow map
void set_norm_atlas(const Ref<Texture2DArray> &p_atlas); // PBR tangent-space normal map
void set_projection_table(const PackedFloat32Array &p_table); // per-face UV projections (12 floats/entry)
void set_use_textures(bool p_val); // sample the atlas instead of flat debug colors
bool get_use_textures() const;
void set_adaptive_arch_lod(bool p_val); // experimental: refine architecture region to 2x
bool get_adaptive_arch_lod() const;
void set_arch_min_lod(int p_lod); // refinement floor when active (-1=2x, -2=4x)
int get_arch_min_lod() const;
void set_use_shortest_diagonal(bool p_val);
bool get_use_shortest_diagonal() const;
void set_use_mesh_simplify(bool p_val);
bool get_use_mesh_simplify() const;
void set_debug_logging(bool p_val);
bool get_debug_logging() const;
void set_debug_seams(bool p_val);
bool get_debug_seams() const;
void set_debug_clipmap(bool p_val);
bool get_debug_clipmap() const;
// Collision mode: trimesh (physics bodies), SDF (density queries), or both
enum CollisionMode { COLLISION_TRIMESH = 0, COLLISION_SDF = 1, COLLISION_BOTH = 2 };
void set_collision_mode(int p_mode);
int get_collision_mode() const;
void set_collision_lod_max(int p_val);
int get_collision_lod_max() const;
void set_collision_distance(float p_val);
float get_collision_distance() const;
// -- Stats (read-only, for HUD) --
int get_chunk_count() const;
int get_active_chunk_count() const;
int get_pending_count() const;
int get_collision_queue_size() const;
int get_splits_this_frame() const;
// -- Density sampling (for spawn placement, etc.) --
bool is_using_builtin_generator() const { return using_builtin_generator_; }
// SCALAR QUERY VOLUME. hyper-games #64 NEXT 3 asks for a number instead of
// a guess about the scalar path, and there was no counter to read.
//
// Two entry points cover all of it: sample_density (map scripts -- spawn
// placement, _lift_out_of_terrain, the fall-respawn) and _eval_with_edits
// (get_distance, get_surface_normal, get_capsule_push -- the SDF collision
// family, which only runs when collision_mode is SDF).
Dictionary get_scalar_query_counts() const;
void reset_scalar_query_counts();
float sample_density(const Vector3 &position) const;
// THE GPU ORACLE. Runs the real density pipeline -- the same shader, the
// same uniform set, the same buffers the mesher uses -- over a small grid
// and hands the values back. Returns EMPTY if the GPU refused, which is
// itself the answer.
//
// It exists because every gate written before it either ran outside Godot
// or inspected GLSL text, and none could tell a correct shader from one
// that never ran. sample_density() is the CPU twin; comparing the two is
// the parity check test_erosion_parity.cpp does for the wrapper, applied
// to the engine.
//
// A local RenderingDevice is null under --headless, so this only answers
// in a real render session.
PackedFloat32Array debug_gpu_density(const Vector3 &origin, float voxel_size, int cells);
// ── BAKE A VOLUME TO A MESH, ON THE GPU ─────────────────────────────────
//
// Takes the GLSL a proc-nodes ProcVolume carries -- `float proc_density(vec3
// pos)` -- fills a density grid with it and meshes it, both through the
// SAME DCGpuThread the terrain already runs. Returns an ArrayMesh, or null
// if the GPU declined.
//
// WHY HERE AND NOT IN proc-nodes. This needs DCGpuThread and a
// RenderingDevice, which live in dc-terrain/src and cannot move to libdc
// without making libdc godot-dependent. And a SECOND RenderingDevice is not
// an option: a second VkDevice bring-up starves and left two meshers
// running in one world (hyper-games #58). One device, one GPU thread, and
// this borrows it.
//
// The GLSL takes a String rather than a ProcVolume because dc-terrain has
// no compile-time dependency on proc-nodes' classes -- the same reason
// DCTerrainGeneratorProcNodes reaches the graph through reflection.
Ref<ArrayMesh> bake_volume_mesh(const String &p_glsl, const Vector3 &p_origin,
float p_voxel_size, int p_cells);
// THE COLLISION MESH, as triangles, so it can be drawn.
//
// Collision is built through PhysicsServer3D as bare RIDs hung off the
// clipmap nodes, not as scene-tree CollisionShape3Ds -- so nothing in
// GDScript could see it, and "is the collision where the terrain is" had no
// answer but walking into things. This reads the concave shapes back and
// returns their faces in WORLD space, ready for a debug overlay.
//
// Gaps in what comes back are the finding: collision is only built for
// lod_level <= collision_lod_max (5 by default), so beyond that there is
// nothing to stand on however solid the terrain looks.
PackedVector3Array debug_collision_faces(const Vector3 ¢er, float radius) const;
// The DRAWN triangles in world space, for comparison against the above.
PackedVector3Array debug_visual_faces(const Vector3 ¢er, float radius) const;
// WHICH BINARY IS THIS. Baked at compile time, so a report can name the
// artifact it examined instead of assuming the .so matches the source --
// the rule hyper-games #44 wrote and #45 broke within hours. The .so under
// addons/ is a symlink into dc-terrain/bin and is not rebuilt by anything
// automatic, so "I changed the C++" and "the game runs the change" are
// independent facts.
String build_stamp() const;
// The streaming state of the leaf under a point: lod, voxel_size,
// chunk_size, state, triangles, has_collision, collision_eligible, origin.
// EMPTY dictionary when no leaf contains the point -- distinct from LOD 0.
Dictionary debug_chunk_info(const Vector3 &position) const;
// -- SDF collision API (direct density field queries) --
float _eval_with_edits(float x, float y, float z) const;
// Returns signed distance to terrain surface (positive = inside solid, negative = in air).
// Use for character controller collision instead of mesh-based physics.
float get_distance(const Vector3 &position) const;
// Returns surface normal at position (normalized gradient of density field).
Vector3 get_surface_normal(const Vector3 &position) const;
// Capsule collision query: returns push-out vector if capsule intersects terrain.
// Returns zero vector if no collision. Tests bottom, center, and top of capsule.
Vector3 get_capsule_push(const Vector3 &position, float radius, float height) const;
// -- Terrain editing --
// interactive: true = a LIVE in-the-moment carve -> worker PRIORITY lane (instant
// feedback). false = a replayed/durable-synced edit -> already baked into the edit
// list, so re-mesh at NORMAL priority and do NOT flood the priority lane (which
// starves LOD pre-mesh). Default true keeps every direct/legacy caller on the old
// priority behavior.
void edit_terrain(const Vector3 &position, float radius, float strength, int op = 0, bool interactive = true, int material_id = 0);
// persistent = MAP-AUTHORED LANDSCAPE: survives clear_edits(), same as
// stamp_mesh_arrays. The level shell's MOAT FALLBACK must pass true.
void edit_terrain_box(const Vector3 &position, const Vector3 &half_extents, float strength,
bool interactive = true, int material_id = 0, bool persistent = false);
void stamp_mesh_obj(const String &obj_path, const Vector3 &position, float scale, float strength, bool interactive = true, int material_id = 0);
void debug_sdf_at(const Vector3 &position); // Print SDF values from all edits at this position
// persistent = MAP-AUTHORED LANDSCAPE: survives clear_edits(). See
// TerrainEdit::persistent. The level shell subtract must pass true.
void stamp_mesh_arrays(const PackedVector3Array &vertices, const PackedInt32Array &indices,
const Vector3 &position, float scale, float strength, bool interactive = true,
int material_id = 0, bool persistent = false);
// Convex brush = intersection of half-space planes (a Quake brush). planes is a
// flat [nx,ny,nz,d, ...] (outward normal, inside = n·p <= d). center+radius = the
// bounding sphere for chunk culling.
void edit_terrain_convex(const PackedFloat32Array &planes, const Vector3 ¢er,
float radius, float strength, bool interactive = true, int material_id = 0,
const PackedInt32Array &face_proj = PackedInt32Array());
void clear_edits();
// Drop map-authored landscape only (keeps the player journal), so a map can
// re-derive its shell at runtime. Does not rebuild — stamp right after.
void clear_landscape_edits();
int get_edit_count() const;
// Interactive brush editing (test mutability)
int pick_brush(const Vector3 &p) const;
Vector3 get_brush_center(int idx) const;
float get_brush_radius(int idx) const;
PackedVector3Array get_brush_hull_triangles(int idx) const;
Dictionary lift_brush(int idx);
void move_brush(int idx, const Vector3 &delta);
Array get_edits_as_array() const;
void load_edits_from_array(const Array &edits_array);
// -- CSG primitives --
// type: 0=sphere, 1=box. op: 0=union, 1=subtract, 2=intersect.
// group: primitives in same group are combined, then UNION'd with terrain.
// size: sphere=[radius,0,0], box=[half_x, half_y, half_z].
void add_csg_primitive(int type, int op, int group,
const Vector3 &position, const Vector3 &size);
void clear_csg_primitives();
int get_csg_primitive_count() const;
void set_csg_primitive_position(int index, const Vector3 &position);
void set_csg_primitive_size(int index, const Vector3 &size);
void remove_csg_primitive(int index);
// -- Noise toggle --
void set_use_noise(bool p_val);
bool get_use_noise() const;
// -- Spherical/planetoid mode --
void set_spherical(bool p_val);
bool get_spherical() const;
void set_floating_islands(bool p_val);
bool get_floating_islands() const;
void set_island_radius(float p_val);
float get_island_radius() const;
void set_island_depth_ratio(float p_val);
float get_island_depth_ratio() const;
void set_island_spacing(float p_val);
float get_island_spacing() const;
void set_planet_radius(float p_val);
float get_planet_radius() const;
void set_max_noise_octaves(int p_val);
int get_max_noise_octaves() const;
void set_max_ridge_octaves(int p_val);
int get_max_ridge_octaves() const;
// -- Direct eval mode (Gildea path for all terrain, not just CSG) --
void set_force_direct_eval(bool p_val);
bool get_force_direct_eval() const;
void set_csg_refinement(int p_val);
int get_csg_refinement() const;
// -- Generator resource (overrides built-in noise when set) --
void set_generator(const Ref<DCTerrainGenerator> &p_generator);
Ref<DCTerrainGenerator> get_generator() const;
// -- GPU compute pipeline --
void set_use_gpu(bool p_val);
bool get_use_gpu() const;
// -- LOD tuning --
void set_lod_factor(float p_val);
float get_lod_factor() const;
void set_hysteresis(float p_val);
float get_hysteresis() const;
void set_fade_speed(float p_val);
float get_fade_speed() const;
void set_debug_lod_colors(bool p_val);
bool get_debug_lod_colors() const;
void set_debug_wireframe(bool p_val);
bool get_debug_wireframe() const;
void set_debug_edits(bool p_val);
bool get_debug_edits() const;
void set_normal_perturbation(float p_val);
float get_normal_perturbation() const;
// -- Screen-space error LOD --
void set_screen_split_threshold(float p_val);
float get_screen_split_threshold() const;
void set_use_screen_space_error(bool p_val);
bool get_use_screen_space_error() const;
// -- LOD mode (Eager Fade vs Blocking Swap) --
void set_lod_mode(int p_mode);
int get_lod_mode() const;
// -- LOD freeze (call during grapple/teleport to prevent thrashing) --
void set_lod_frozen(bool p_frozen);
bool get_lod_frozen() const;
void debug_kick_lod(); // Debug: force one LOD re-traversal (pure invalidate_settled, no freeze/camera move)
private:
void _upload_cell(CellResult& result);
void _free_node_rids(OctreeClipmapNode* node);
void _requeue_affected_chunks(const float center[3], float radius);
// Shared by both stamp_mesh_* entry points. Was duplicated in each, which is
// how both missed the hardening edit_terrain_box received. Returns the count
// re-queued; reports what it had to defer to the post-upload path.
int _requeue_chunks_for_mesh_stamp(const Vector3 ¢er, float radius, bool interactive);
static void _on_node_free(OctreeClipmapNode* node, void* userdata);
static void _on_mesh_free(OctreeClipmapNode* node, void* userdata);
static void _on_collision_free(OctreeClipmapNode* node, void* userdata);
void _propagate_config();
void _drain_workers(); // poll & discard all in-flight results
void _update_debug_seams();
void _generate_seam_for_node(OctreeClipmapNode* node);
void _upload_seam_mesh(OctreeClipmapNode* node, const DCMeshData& mesh_data);
// Density engine (pure C++)
DCDensity density_;
DensityConfig config_;
// Generator abstraction — routes evaluate() through DCGenerator interface
DCGeneratorNoise noise_generator_; // default noise generator (wraps density_)
DCGenerator* generator_ptr_ = nullptr; // active generator pointer
// Warn-once latch for the built-in-generator substitution below.
bool warned_generator_fallback_ = false;
bool using_builtin_generator_ = true;
// ⚠ HOLDS THE BAKED GLSL ALIVE. DCGpuThread caches the custom_glsl POINTER
// and compares by pointer, not by string -- so handing it a temporary's
// c_str() leaves it holding a dangling address, and a later allocation
// landing there would read as "same shader" and reuse the wrong compiled
// one. Silently. This member outlives every submit.
std::string baked_volume_glsl_;
// mutable + atomic: the query entry points are const and DCCollisionSDF is
// reachable from more than one thread.
mutable std::atomic<uint64_t> n_sample_density_{0};
mutable std::atomic<uint64_t> n_eval_with_edits_{0};
double scalar_profile_accum_ = 0.0;
bool scalar_profile_on_ = false;
void _fallback_to_builtin(const char *where);
Ref<DCTerrainGenerator> generator_resource_; // user-assigned Resource (inspector/script)
// Octree clipmap (replaces DCChunkManager)
OctreeClipmap octree_clipmap_;
// Worker threads with dedicated GPU pipelines (replaces DCGpuThread + DCThreadPool)
std::vector<std::unique_ptr<DCWorkerCPU>> workers_; // game mode: auto-scaled to CPU cores
std::unique_ptr<DCGpuThread> gpu_thread_; // shared GPU density fill thread
// RT/CLAS removed — re-add after CPU MVP working
uint32_t frame_counter_ = 0;
int next_worker_ = 0; // round-robin dispatch
// Editor mode: shared single worker across all DCTerrain instances
// (multiple RenderingDevices crash on some GPUs)
static std::unique_ptr<DCWorkerCPU> s_editor_worker_;
static int s_editor_refcount_;
bool using_editor_worker_ = false;
// Unified access: returns either per-instance or shared editor workers
bool _has_workers() const;
bool _submit_to_worker(const CellRequest& req);
bool _submit_to_worker_priority(const CellRequest& req); // edit lane (1b)
size_t _worker_priority_depth() const; // total queued edits across workers (1b)
void _poll_workers(std::function<void(CellResult&)> handler, double budget_ms, int min_count);
void _set_worker_generation(uint32_t gen);
// Spawn the game-mode worker pool. Split out of _notification so the
// deferred path (waiting on GPU bring-up) can call it from _process.
void _spawn_workers();
// Size of the pre-mesh MESH_PENDING backlog as of the last
// _dispatch_pre_mesh_requests pass. Instrumentation only (FRAME pm=<n>):
// this backlog drives the dispatch-phase cost, and it has no distance
// eviction, so it is the number to watch.
int premesh_pending_last_frame_ = 0;
bool workers_deferred_ = false; // workers held back until the GPU is up
std::chrono::steady_clock::time_point workers_deferred_since_;
void _drain_all_workers();
uint32_t generation_ = 0; // incremented on rebuild — stale results discarded
float prev_cam_pos_[3] = {0.0f, 0.0f, 0.0f}; // teleport detection
// Terrain display properties
Color terrain_color_ = Color(0.3f, 0.5f, 0.25f);
Ref<Material> custom_material_; // When set, overrides internal per-LOD materials
float voxel_size_ = 1.0f;
int octree_depth_ = 5;
bool seamless_lod_ = false; // Force all chunks to LOD 0 (no inter-chunk seams)
// Per-LOD materials (unified ShaderMaterial — same shader for settled + morphing chunks)
// 16, not 12. This caps the per-LOD material array, and a depth-14 clipmap has
// LOD levels 0..13; at 12 the coarsest two would share slot 11, breaking the
// per-LOD fade_t crossfade and the debug LOD colours at exactly the distances
// a long view exists to show. It does not gate geometry -- octree_depth is
// unclamped -- so this is about the far LODs looking right, not existing.
static constexpr int MAX_LOD_MATERIALS = 16;
Ref<ShaderMaterial> lod_materials_[MAX_LOD_MATERIALS];
Ref<Texture2DArray> tex_atlas_; // WAD texture atlas (layer index == material_id)
Ref<Texture2DArray> glow_atlas_; // PBR: RGBA — rgb emissive glow, a gloss (→ roughness)
Ref<Texture2DArray> norm_atlas_; // PBR: tangent-space normal map
Ref<Texture2D> proj_table_tex_; // per-face projection table as an RGBA32F data texture
// (width = 3 texels/entry, height = num entries); sampled
// by the shader via the per-vertex projection index.
int proj_table_count_ = 0; // number of projection entries (rows) in proj_table_tex_
bool use_textures_ = false; // false = flat debug colors (default), true = atlas
void _apply_texture_params(const Ref<ShaderMaterial> &mat) const; // set atlas+toggle on a mat
// Unified terrain shader (used by ALL chunks at ALL times)
Ref<Shader> terrain_shader_;
// Per-chunk ShaderMaterial copies — only for actively-fading chunks (~16-32 at a time).
// When fade completes, the entry is removed and the chunk returns to the shared lod_materials_.
std::unordered_map<OctreeClipmapNode*, Ref<ShaderMaterial>> fade_overrides_;
// Terrain edit list (owned here, snapshot shared via DensityConfig)
DCEditList edit_list_;
// Keep ArrayMesh refs alive per-node (prevent GC while RS holds RIDs).
// Key = OctreeClipmapNode pointer. Cleared when node is freed.
std::unordered_map<OctreeClipmapNode*, Ref<ArrayMesh>> node_meshes_;
std::unordered_map<OctreeClipmapNode*, Ref<ArrayMesh>> seam_meshes_;
// Track freed node pointers to prevent use-after-free when processing
// worker results. Cleared at start of each frame.
std::unordered_set<OctreeClipmapNode*> freed_nodes_;
// Deferred deallocation queue for merged/freed nodes.
// Stores nodes that need RID cleanup (1 per frame to avoid stutter).
struct DeallocEntry {
RID rs_instance;
RID collision_body;
RID collision_shape;
RID seam_mesh_instance;
Ref<ArrayMesh> mesh; // prevent GC until RS frees the instance
};
std::deque<DeallocEntry> dealloc_queue_;
// Trimesh collision manager (queued concave polygon shapes)
DCCollisionTrimesh collision_trimesh_;
// Collision mode
CollisionMode collision_mode_ = COLLISION_TRIMESH;
// Seam mesh generation — nodes waiting for neighbors to become ACTIVE
struct SeamRequest {
OctreeClipmapNode* node;
uint32_t node_generation = 0;
};
std::vector<SeamRequest> seam_pending_;
// LOD tuning
float lod_factor_ = 3.0f;
float hysteresis_ = 1.3f;
float fade_speed_ = 3.0f;
float normal_perturbation_ = 0.12f;
bool debug_lod_colors_ = true;
bool debug_wireframe_ = false;
// LOD mode
LodMode lod_mode_ = LOD_BLOCKING_SWAP;
// BLOCKING_SWAP mode helpers (pre-mesh cache approach)
void _dispatch_pre_mesh_requests(int regular_dispatched_this_frame);
void _apply_cached_meshes();
// LOD freeze — skips octree traversal when true.
bool lod_frozen_ = false;
// Mesh quality flags (toggleable for A/B testing)
bool use_adaptive_qef_ = true; // Eigenvalue-adaptive QEF regularization
bool adaptive_arch_lod_ = false; // experimental crease/architecture sub-LOD (off by default)
void _update_arch_bounds(); // recompute the architecture AABB from convex edits
bool use_shortest_diagonal_ = true; // Aspect-ratio-aware quad splitting
bool use_mesh_simplify_ = true; // meshoptimizer post-process on GPU path
// Debug
bool debug_logging_ = false;
bool debug_seams_ = false;
bool debug_clipmap_ = false;
bool debug_edits_ = false;
MeshInstance3D* debug_mesh_node_ = nullptr;
MeshInstance3D* debug_clipmap_node_ = nullptr;
MeshInstance3D* debug_edits_node_ = nullptr;
int debug_frames_ = 0;
void _update_debug_clipmap();
void _update_debug_edits();
};
} // namespace godot
#endif // DC_TERRAIN_H