mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 21:29:19 +02:00
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
163 lines
5.5 KiB
C++
163 lines
5.5 KiB
C++
/*
|
|
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
|