#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LodExportConfig {
pub lod_count: usize,
pub reduction_ratios: Vec<f32>,
pub merge_threshold: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LodChainLevel {
pub level: usize,
pub vertex_count: usize,
pub triangle_count: usize,
pub reduction: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LodChain {
pub levels: Vec<LodChainLevel>,
pub base_vertex_count: usize,
pub base_triangle_count: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LodExportResult {
pub chain: LodChain,
pub total_levels: usize,
pub success: bool,
}
#[allow(dead_code)]
pub fn default_lod_export_config() -> LodExportConfig {
LodExportConfig {
lod_count: 4,
reduction_ratios: vec![1.0, 0.5, 0.25, 0.125],
merge_threshold: 1e-4,
}
}
#[allow(dead_code)]
pub fn generate_lod_chain(
positions: &[[f32; 3]],
triangles: &[[u32; 3]],
cfg: &LodExportConfig,
) -> LodExportResult {
let base_vertex_count = positions.len();
let base_triangle_count = triangles.len();
let mut levels = Vec::with_capacity(cfg.lod_count);
let ratios = &cfg.reduction_ratios;
for i in 0..cfg.lod_count {
let ratio = if i < ratios.len() { ratios[i] } else { 0.125 };
let ratio_clamped = ratio.clamp(0.0, 1.0);
let tri_count = ((base_triangle_count as f32 * ratio_clamped).ceil() as usize).max(1);
let vtx_count =
((base_vertex_count as f32 * ratio_clamped.sqrt()).ceil() as usize).max(1);
levels.push(LodChainLevel {
level: i,
vertex_count: vtx_count,
triangle_count: tri_count,
reduction: ratio_clamped,
});
}
let total_levels = levels.len();
let chain = LodChain {
levels,
base_vertex_count,
base_triangle_count,
};
LodExportResult {
chain,
total_levels,
success: true,
}
}
#[allow(dead_code)]
pub fn lod_chain_level_to_json(l: &LodChainLevel) -> String {
format!(
r#"{{"level":{},"vertex_count":{},"triangle_count":{},"reduction":{:.4}}}"#,
l.level, l.vertex_count, l.triangle_count, l.reduction
)
}
#[allow(dead_code)]
pub fn lod_chain_to_json(chain: &LodChain) -> String {
let levels_json: Vec<String> = chain.levels.iter().map(lod_chain_level_to_json).collect();
format!(
r#"{{"base_vertex_count":{},"base_triangle_count":{},"levels":[{}]}}"#,
chain.base_vertex_count,
chain.base_triangle_count,
levels_json.join(",")
)
}
#[allow(dead_code)]
pub fn lod_export_result_to_json(r: &LodExportResult) -> String {
format!(
r#"{{"total_levels":{},"success":{},"chain":{}}}"#,
r.total_levels,
r.success,
lod_chain_to_json(&r.chain)
)
}
#[allow(dead_code)]
pub fn lod_level_count(result: &LodExportResult) -> usize {
result.total_levels
}
#[allow(dead_code)]
pub fn lod_reduction_at(result: &LodExportResult, level: usize) -> f32 {
result
.chain
.levels
.get(level)
.map(|l| l.reduction)
.unwrap_or(0.0)
}
#[allow(dead_code)]
pub fn total_triangle_budget(result: &LodExportResult) -> usize {
result
.chain
.levels
.iter()
.map(|l| l.triangle_count)
.sum()
}
#[allow(dead_code)]
pub fn lod_config_to_json(cfg: &LodExportConfig) -> String {
let ratios: Vec<String> = cfg
.reduction_ratios
.iter()
.map(|r| format!("{:.4}", r))
.collect();
format!(
r#"{{"lod_count":{},"merge_threshold":{:.6},"reduction_ratios":[{}]}}"#,
cfg.lod_count,
cfg.merge_threshold,
ratios.join(",")
)
}
#[allow(dead_code)]
pub fn validate_lod_chain(chain: &LodChain) -> bool {
if chain.levels.is_empty() {
return false;
}
let first = &chain.levels[0];
chain
.levels
.iter()
.all(|l| l.triangle_count <= first.triangle_count)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_positions() -> Vec<[f32; 3]> {
vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
]
}
fn sample_triangles() -> Vec<[u32; 3]> {
vec![[0, 1, 2], [1, 3, 2]]
}
#[test]
fn default_config_has_four_levels() {
let cfg = default_lod_export_config();
assert_eq!(cfg.lod_count, 4);
assert_eq!(cfg.reduction_ratios.len(), 4);
}
#[test]
fn generate_lod_chain_level_count_matches_config() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
assert_eq!(result.total_levels, cfg.lod_count);
assert_eq!(result.chain.levels.len(), cfg.lod_count);
}
#[test]
fn generate_lod_chain_level0_full_resolution() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
assert_eq!(
result.chain.levels[0].triangle_count,
result.chain.base_triangle_count
);
}
#[test]
fn lod_level_count_matches_total_levels() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
assert_eq!(lod_level_count(&result), result.total_levels);
}
#[test]
fn lod_reduction_at_out_of_range_returns_zero() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
assert!((lod_reduction_at(&result, 999) - 0.0).abs() < f32::EPSILON);
}
#[test]
fn total_triangle_budget_positive() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
assert!(total_triangle_budget(&result) > 0);
}
#[test]
fn lod_chain_to_json_contains_base_vertex_count() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
let json = lod_chain_to_json(&result.chain);
assert!(
json.contains(&format!(
"\"base_vertex_count\":{}",
result.chain.base_vertex_count
))
);
}
#[test]
fn validate_lod_chain_valid() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
assert!(validate_lod_chain(&result.chain));
}
#[test]
fn lod_config_to_json_contains_lod_count() {
let cfg = default_lod_export_config();
let json = lod_config_to_json(&cfg);
assert!(json.contains("\"lod_count\":4"));
}
#[test]
fn lod_export_result_to_json_contains_success() {
let pos = sample_positions();
let tri = sample_triangles();
let cfg = default_lod_export_config();
let result = generate_lod_chain(&pos, &tri, &cfg);
let json = lod_export_result_to_json(&result);
assert!(json.contains("\"success\":true"));
}
}