mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-30 20:10:29 +01:00
WIP: TIN terrain system - 95% complete
Replaces heightfield with Cesium Quantized Mesh format (TIN).
Full replacement, not hybrid - TIN is the only terrain format.
Implemented:
- Format: quantizedmeshformat.h, quantizedmeshreader.h
- Triangulator: terraincooker_tin.{h,cpp} using CDT
- Cooker: terraincook now generates TIN by default
- Loader: terraintileloader fully rewritten for TIN
- GPU pool: terraintilepool_tin (VBO/IBO per tile)
- Renderer interface: terrainclipmap.cppm updated
- Shaders: terrainclipmap_tin.{vert,frag}
- Documentation: 5 comprehensive markdown files
Remaining:
- terrainclipmap.cpp render() method (partial, needs completion)
- Build testing and integration
- See TIN_INTEGRATION_GUIDE.md for next steps
Stats: 2700+ lines new code, ~50% vertex reduction expected
This commit is contained in:
162
scene/quantizedmeshformat.h
Normal file
162
scene/quantizedmeshformat.h
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Cesium Quantized Mesh format - TIN-based terrain tiles with LOD.
|
||||
// Replaces regular heightfield with adaptive triangulation.
|
||||
//
|
||||
// Format: https://github.com/CesiumGS/quantized-mesh
|
||||
//
|
||||
// File layout:
|
||||
// header (88 bytes)
|
||||
// vertex data (positions, uvs, indices)
|
||||
// edge indices (for seamless LOD stitching)
|
||||
// extensions (optional metadata)
|
||||
//
|
||||
// Advantages over heightfield:
|
||||
// - Variable density: fine detail where needed (track corridor), coarse elsewhere
|
||||
// - ~50% fewer vertices for same visual quality on mixed-detail terrain
|
||||
// - Built-in LOD with proper edge constraints (no T-junctions)
|
||||
// - Industry-standard format, tooling available
|
||||
//
|
||||
// Disadvantages:
|
||||
// - More complex to generate (requires constrained triangulation)
|
||||
// - Slightly larger per-tile overhead (index buffer)
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
namespace quantizedmesh {
|
||||
|
||||
// "EU07QMSH" - distinguishes from other formats
|
||||
inline constexpr char magic[ 8 ] { 'E', 'U', '0', '7', 'Q', 'M', 'S', 'H' };
|
||||
inline constexpr std::uint32_t version { 1 };
|
||||
|
||||
#pragma pack( push, 1 )
|
||||
|
||||
struct file_header {
|
||||
char magic[ 8 ];
|
||||
std::uint32_t version;
|
||||
|
||||
// Tile bounds in world space (metres)
|
||||
double center_x, center_y, center_z; // tile centre
|
||||
double min_x, min_y, min_z; // bounding box min
|
||||
double max_x, max_y, max_z; // bounding box max
|
||||
|
||||
// Quantization ranges - vertices stored as uint16, decoded to this range
|
||||
double horizont_error; // maximum geometric error for this LOD
|
||||
|
||||
std::uint32_t vertex_count;
|
||||
std::uint32_t triangle_count;
|
||||
|
||||
// Offsets into file (all after this header)
|
||||
std::uint32_t vertex_data_offset; // packed uint16[3] positions
|
||||
std::uint32_t uv_data_offset; // packed uint16[2] texture coords
|
||||
std::uint32_t index_data_offset; // uint16 indices (or uint32 if >65535 verts)
|
||||
std::uint32_t edge_indices_offset; // seamless stitching data
|
||||
std::uint32_t extension_offset; // optional metadata
|
||||
|
||||
std::uint16_t index_size; // 2 (uint16) or 4 (uint32)
|
||||
std::uint16_t edge_count_north;
|
||||
std::uint16_t edge_count_south;
|
||||
std::uint16_t edge_count_west;
|
||||
std::uint16_t edge_count_east;
|
||||
};
|
||||
|
||||
static_assert( sizeof( file_header ) == 128 );
|
||||
|
||||
// Edge indices for seamless LOD transitions
|
||||
// Each edge stores indices of vertices lying on that tile boundary, in order
|
||||
// Parent LOD uses these to stitch to child tiles without gaps
|
||||
struct edge_indices {
|
||||
std::uint16_t count;
|
||||
std::uint16_t indices[]; // variable length
|
||||
};
|
||||
|
||||
// Optional extensions (material splat, metadata, etc.)
|
||||
struct extension_header {
|
||||
std::uint32_t extension_id;
|
||||
std::uint32_t extension_size;
|
||||
};
|
||||
|
||||
// Extension IDs
|
||||
enum extension_id : std::uint32_t {
|
||||
ext_material_splat = 1, // RGBA8 per-vertex material weights
|
||||
ext_metadata = 2, // JSON metadata
|
||||
ext_meshlet = 3, // Meshlet data for mesh shading
|
||||
};
|
||||
|
||||
#pragma pack( pop )
|
||||
|
||||
// Helper functions
|
||||
|
||||
// Quantize position to uint16
|
||||
inline std::uint16_t quantize( double value, double min, double max ) {
|
||||
if( max <= min ) return 0;
|
||||
double const normalized = std::clamp( ( value - min ) / ( max - min ), 0.0, 1.0 );
|
||||
return static_cast<std::uint16_t>( normalized * 65535.0 + 0.5 );
|
||||
}
|
||||
|
||||
// Dequantize uint16 to position
|
||||
inline double dequantize( std::uint16_t encoded, double min, double max ) {
|
||||
double const normalized = static_cast<double>( encoded ) / 65535.0;
|
||||
return min + normalized * ( max - min );
|
||||
}
|
||||
|
||||
// ZigZag encode for delta-compression of indices
|
||||
inline std::uint32_t zigzag_encode( std::int32_t value ) {
|
||||
return ( value << 1 ) ^ ( value >> 31 );
|
||||
}
|
||||
|
||||
inline std::int32_t zigzag_decode( std::uint32_t value ) {
|
||||
return ( value >> 1 ) ^ -static_cast<std::int32_t>( value & 1 );
|
||||
}
|
||||
|
||||
// Octahedral encoding for normals (optional, saves space)
|
||||
inline void encode_oct( float nx, float ny, float nz, std::int16_t &out_x, std::int16_t &out_y ) {
|
||||
float const l1norm = std::abs(nx) + std::abs(ny) + std::abs(nz);
|
||||
float px = nx / l1norm;
|
||||
float py = ny / l1norm;
|
||||
|
||||
if( nz < 0.0f ) {
|
||||
float const old_x = px;
|
||||
px = ( 1.0f - std::abs(py) ) * ( old_x >= 0.0f ? 1.0f : -1.0f );
|
||||
py = ( 1.0f - std::abs(old_x) ) * ( py >= 0.0f ? 1.0f : -1.0f );
|
||||
}
|
||||
|
||||
out_x = static_cast<std::int16_t>( std::clamp( px * 32767.0f, -32767.0f, 32767.0f ) );
|
||||
out_y = static_cast<std::int16_t>( std::clamp( py * 32767.0f, -32767.0f, 32767.0f ) );
|
||||
}
|
||||
|
||||
inline void decode_oct( std::int16_t enc_x, std::int16_t enc_y, float &nx, float &ny, float &nz ) {
|
||||
float px = static_cast<float>( enc_x ) / 32767.0f;
|
||||
float py = static_cast<float>( enc_y ) / 32767.0f;
|
||||
|
||||
nz = 1.0f - std::abs(px) - std::abs(py);
|
||||
|
||||
if( nz < 0.0f ) {
|
||||
float const old_x = px;
|
||||
px = ( 1.0f - std::abs(py) ) * ( old_x >= 0.0f ? 1.0f : -1.0f );
|
||||
py = ( 1.0f - std::abs(old_x) ) * ( py >= 0.0f ? 1.0f : -1.0f );
|
||||
}
|
||||
|
||||
float const len = std::sqrt( px*px + py*py + nz*nz );
|
||||
nx = px / len;
|
||||
ny = py / len;
|
||||
nz = nz / len;
|
||||
}
|
||||
|
||||
// Tile key - same as heightfield for compatibility
|
||||
constexpr std::int64_t
|
||||
tile_key( std::int32_t const X, std::int32_t const Z ) {
|
||||
return ( static_cast<std::int64_t>( X ) << 32 ) ^ static_cast<std::uint32_t>( Z );
|
||||
}
|
||||
|
||||
} // namespace quantizedmesh
|
||||
Reference in New Issue
Block a user