17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 12:09:19 +02:00
Files
maszyna/scene/quantizedmeshreader.h
maj00r 5f34d68e6f 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
2026-09-17 23:02:13 +02:00

224 lines
7.2 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
// Reads Cesium Quantized Mesh tiles - replaces heightfield reader entirely.
// Used by terrain_tile_loader to stream TIN tiles from disk.
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <memory>
#include "scene/quantizedmeshformat.h"
namespace quantizedmesh {
// Reader for cooked TIN terrain tiles (.qm files)
// Single-threaded, meant to be used from terrain_tile_loader worker thread
class reader {
public:
reader() = default;
~reader() { close(); }
reader( reader const & ) = delete;
reader &operator=( reader const & ) = delete;
// Open tile directory (contains terrain_X_Z_lodN.qm files)
bool open( std::string const &Directory );
void close();
// Read single LOD of a tile
struct tile_data {
// Dequantized positions
std::vector<double> positions_x;
std::vector<double> positions_y;
std::vector<double> positions_z;
// Texture coordinates
std::vector<float> uvs_u;
std::vector<float> uvs_v;
// Triangle indices
std::vector<std::uint32_t> indices;
// Material per vertex (index into material table)
std::vector<std::uint8_t> materials;
// Edge indices for stitching
std::vector<std::uint16_t> north_edge;
std::vector<std::uint16_t> south_edge;
std::vector<std::uint16_t> west_edge;
std::vector<std::uint16_t> east_edge;
// Tile bounds (for positioning)
double center_x, center_y, center_z;
double min_x, min_y, min_z;
double max_x, max_y, max_z;
double geometric_error;
};
bool read_tile( std::int32_t X, std::int32_t Z, std::uint32_t LOD, tile_data &Out );
// Check if tile exists
bool contains( std::int32_t X, std::int32_t Z, std::uint32_t LOD ) const;
std::string const &directory() const { return m_directory; }
private:
std::string tile_path( std::int32_t X, std::int32_t Z, std::uint32_t LOD ) const;
std::string m_directory;
};
// Implementation
inline bool reader::open( std::string const &Directory ) {
close();
m_directory = Directory;
return true; // Lazy - tiles opened on demand
}
inline void reader::close() {
m_directory.clear();
}
inline std::string reader::tile_path( std::int32_t X, std::int32_t Z, std::uint32_t LOD ) const {
char filename[ 256 ];
std::snprintf( filename, sizeof( filename ), "terrain_%d_%d_lod%u.qm", X, Z, LOD );
return m_directory + "/" + filename;
}
inline bool reader::contains( std::int32_t X, std::int32_t Z, std::uint32_t LOD ) const {
auto const path = tile_path( X, Z, LOD );
FILE *f = std::fopen( path.c_str(), "rb" );
if( !f ) return false;
std::fclose( f );
return true;
}
inline bool reader::read_tile( std::int32_t X, std::int32_t Z, std::uint32_t LOD, tile_data &Out ) {
auto const path = tile_path( X, Z, LOD );
FILE *file = std::fopen( path.c_str(), "rb" );
if( !file ) return false;
// Read header
file_header header;
if( std::fread( &header, sizeof( header ), 1, file ) != 1 ) {
std::fclose( file );
return false;
}
// Verify magic
if( std::memcmp( header.magic, magic, sizeof( magic ) ) != 0 ) {
std::fclose( file );
return false;
}
if( header.version != version ) {
std::fclose( file );
return false;
}
// Store bounds
Out.center_x = header.center_x;
Out.center_y = header.center_y;
Out.center_z = header.center_z;
Out.min_x = header.min_x;
Out.min_y = header.min_y;
Out.min_z = header.min_z;
Out.max_x = header.max_x;
Out.max_y = header.max_y;
Out.max_z = header.max_z;
Out.geometric_error = header.horizont_error;
// Read quantized positions
std::fseek( file, header.vertex_data_offset, SEEK_SET );
std::vector<std::uint16_t> quantized_x( header.vertex_count );
std::vector<std::uint16_t> quantized_y( header.vertex_count );
std::vector<std::uint16_t> quantized_z( header.vertex_count );
for( std::uint32_t i = 0; i < header.vertex_count; ++i ) {
std::fread( &quantized_x[ i ], sizeof( std::uint16_t ), 1, file );
std::fread( &quantized_y[ i ], sizeof( std::uint16_t ), 1, file );
std::fread( &quantized_z[ i ], sizeof( std::uint16_t ), 1, file );
}
// Dequantize positions
Out.positions_x.resize( header.vertex_count );
Out.positions_y.resize( header.vertex_count );
Out.positions_z.resize( header.vertex_count );
for( std::uint32_t i = 0; i < header.vertex_count; ++i ) {
Out.positions_x[ i ] = dequantize( quantized_x[ i ], header.min_x, header.max_x );
Out.positions_y[ i ] = dequantize( quantized_y[ i ], header.min_y, header.max_y );
Out.positions_z[ i ] = dequantize( quantized_z[ i ], header.min_z, header.max_z );
}
// Read UVs
std::fseek( file, header.uv_data_offset, SEEK_SET );
Out.uvs_u.resize( header.vertex_count );
Out.uvs_v.resize( header.vertex_count );
for( std::uint32_t i = 0; i < header.vertex_count; ++i ) {
std::uint16_t u, v;
std::fread( &u, sizeof( std::uint16_t ), 1, file );
std::fread( &v, sizeof( std::uint16_t ), 1, file );
Out.uvs_u[ i ] = static_cast<float>( dequantize( u, 0.0, 1.0 ) );
Out.uvs_v[ i ] = static_cast<float>( dequantize( v, 0.0, 1.0 ) );
}
// Read indices
std::fseek( file, header.index_data_offset, SEEK_SET );
Out.indices.resize( header.triangle_count * 3 );
if( header.index_size == 2 ) {
for( std::uint32_t i = 0; i < header.triangle_count * 3; ++i ) {
std::uint16_t idx;
std::fread( &idx, sizeof( std::uint16_t ), 1, file );
Out.indices[ i ] = idx;
}
} else {
std::fread( Out.indices.data(), sizeof( std::uint32_t ), header.triangle_count * 3, file );
}
// Read edge indices
std::fseek( file, header.edge_indices_offset, SEEK_SET );
Out.north_edge.resize( header.edge_count_north );
Out.south_edge.resize( header.edge_count_south );
Out.west_edge.resize( header.edge_count_west );
Out.east_edge.resize( header.edge_count_east );
if( header.edge_count_north > 0 )
std::fread( Out.north_edge.data(), sizeof( std::uint16_t ), header.edge_count_north, file );
if( header.edge_count_south > 0 )
std::fread( Out.south_edge.data(), sizeof( std::uint16_t ), header.edge_count_south, file );
if( header.edge_count_west > 0 )
std::fread( Out.west_edge.data(), sizeof( std::uint16_t ), header.edge_count_west, file );
if( header.edge_count_east > 0 )
std::fread( Out.east_edge.data(), sizeof( std::uint16_t ), header.edge_count_east, file );
// Materials - for now, all zeros (placeholder)
Out.materials.resize( header.vertex_count, 0 );
std::fclose( file );
return true;
}
} // namespace quantizedmesh