OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / libdc / include / dc_level_mesh.h

80 lines · c ref:
#ifndef DC_LEVEL_MESH_H
#define DC_LEVEL_MESH_H

// ---------------------------------------------------------------------------
// dc_level_mesh — csg4 faces -> renderable mesh + per-brush convex collision.
//
// docs/level-mesh-implementation.md §6, §10, §12 step 3.
//
// SHARP EDGES ARE STRUCTURAL, NOT A SETTING. Every face is triangulated with its
// OWN vertices carrying its OWN plane normal. Nothing is welded and nothing is
// smoothed, so a crease is sharp because the geometry says so — no QEF, no
// auto_smooth, no angle threshold. That is the whole point of the exact path:
// hard-surface-research-ledger.md's "fidelity" problem does not arise here, and
// Kobbelt et al. 2001's non-convergence result (reconstructed normals never
// converge to the true normal field) simply does not apply to geometry that IS
// the brush planes.
//
// RENDER vs COLLISION DIVERGE, deliberately (§10):
//   render    = the csg4-CLIPPED visible skin (hidden surface removed)
//   collision = the UNCLIPPED convex brush hull
// Clipping removes hidden SURFACE, not VOLUME. A per-brush convex shape is both
// exact and cheaper than a trimesh.
//
// UVs ARE NOT COMPUTED HERE. Each vertex carries its face's `proj` index (1-based
// into the importer's projection table, 0 = none) plus `material_id`. The
// fragment shader already resolves Quake face projections from that table — the
// existing Stage-2 per-face texturing path. Computing UVs here would duplicate
// it and would drag the table into libdc, which must stay engine-free.
// ---------------------------------------------------------------------------

#include <cstdint>
#include <vector>

#include "dc_csg4.h"

struct DCLevelMeshData {
    // Parallel per-vertex arrays. NOT welded — see the sharp-edge note above.
    std::vector<float>    positions;    // xyz triples
    std::vector<float>    normals;      // xyz triples (the face plane normal)
    std::vector<uint32_t> material;     // per vertex
    std::vector<uint32_t> proj;         // per vertex; 1-based, 0 = none
    std::vector<uint32_t> brush_id;     // per vertex; provenance for picking/editing
    std::vector<int32_t>  indices;      // triangle list

    CSG4Vec3 lo{0,0,0}, hi{0,0,0};
    bool bounds_valid = false;

    uint32_t vertex_count() const   { return (uint32_t)(positions.size() / 3); }
    uint32_t triangle_count() const { return (uint32_t)(indices.size() / 3); }
    void clear();
};

// One convex collision shape per brush: the point set of its UNCLIPPED hull,
// ready for a ConvexPolygonShape3D.
struct DCConvexCollider {
    uint32_t brush_id = 0;
    std::vector<float> points;   // xyz triples
    uint32_t point_count() const { return (uint32_t)(points.size() / 3); }
};

namespace dc_level_mesh {

// Triangulate csg4 faces into a render mesh. Fan per winding; an n-gon costs
// n-2 triangles, matching the counts the csg4 gate asserts.
void build(const std::vector<CSG4Face>& faces, DCLevelMeshData& out);

// Per-brush convex hull points, from the UNCLIPPED face windings (§10).
void build_colliders(const std::vector<CSG4Brush>& brushes,
                     std::vector<DCConvexCollider>& out);

// Largest angular disagreement (radians) between a triangle's geometric normal
// (from its winding) and the face normal stored on its vertices. On exact brush
// geometry this must be ~0 — it is the numeric form of "sharp by construction",
// and it catches winding/orientation bugs that a triangle count cannot.
float max_normal_deviation(const DCLevelMeshData& m);

} // namespace dc_level_mesh

#endif // DC_LEVEL_MESH_H