use avila_vec3d::*;
use avila_mesh::*;
use std::collections::HashMap;
pub type Result<T> = std::result::Result<T, OptimizerError>;
#[derive(Debug, thiserror::Error)]
pub enum OptimizerError {
#[error("Optimization error: {0}")]
OptimizationError(String),
#[error("Mesh error: {0}")]
MeshError(#[from] MeshError),
#[error("Vec3d error: {0}")]
Vec3dError(#[from] Vec3dError),
}
pub struct MeshMerger {
pub vertex_tolerance: f32,
}
impl MeshMerger {
pub fn new() -> Self {
Self {
vertex_tolerance: 0.001, }
}
pub fn merge_scene(&self, scene: &Scene) -> Result<Scene> {
let mut merged_scene = Scene::new();
let mut meshes_by_material: HashMap<Option<String>, Vec<&Mesh>> = HashMap::new();
for mesh in &scene.meshes {
meshes_by_material
.entry(mesh.material_id.clone())
.or_insert_with(Vec::new)
.push(mesh);
}
for (material_id, meshes) in meshes_by_material {
if meshes.is_empty() {
continue;
}
let merged = self.merge_meshes(&meshes)?;
let mut final_mesh = merged;
final_mesh.material_id = material_id.clone();
merged_scene.add_mesh(final_mesh);
}
merged_scene.materials = scene.materials.clone();
Ok(merged_scene)
}
pub fn merge_meshes(&self, meshes: &[&Mesh]) -> Result<Mesh> {
if meshes.is_empty() {
return Err(OptimizerError::OptimizationError("No meshes to merge".into()));
}
if meshes.len() == 1 {
return Ok((*meshes[0]).clone());
}
let total_vertices: usize = meshes.iter().map(|m| m.vertices.len()).sum();
let total_indices: usize = meshes.iter().map(|m| m.indices.len()).sum();
let mut merged = Mesh::with_capacity(total_vertices, total_indices);
for mesh in meshes {
let offset = merged.vertices.len() as u32;
for vertex in &mesh.vertices {
merged.vertices.push(*vertex);
merged.bounds.expand_point(vertex.position);
}
for &index in &mesh.indices {
merged.indices.push(offset + index);
}
}
if self.vertex_tolerance > 0.0 {
self.deduplicate_vertices(&mut merged)?;
}
Ok(merged)
}
fn deduplicate_vertices(&self, mesh: &mut Mesh) -> Result<()> {
let vertex_count = mesh.vertices.len();
if vertex_count == 0 {
return Ok(());
}
let mut remap: Vec<u32> = (0..vertex_count as u32).collect();
let tolerance_sq = self.vertex_tolerance * self.vertex_tolerance;
for i in 0..vertex_count {
if remap[i] != i as u32 {
continue; }
let v1 = &mesh.vertices[i];
for j in (i + 1)..vertex_count {
if remap[j] != j as u32 {
continue;
}
let v2 = &mesh.vertices[j];
let dist_sq = (v1.position - v2.position).length_squared();
if dist_sq < tolerance_sq {
remap[j] = i as u32;
}
}
}
for index in &mut mesh.indices {
*index = remap[*index as usize];
}
let mut used = vec![false; vertex_count];
for &index in &mesh.indices {
used[index as usize] = true;
}
let mut new_vertices = Vec::with_capacity(vertex_count);
let mut compact_remap = vec![0u32; vertex_count];
let mut next_index = 0u32;
for (i, vertex) in mesh.vertices.iter().enumerate() {
if used[i] {
compact_remap[i] = next_index;
new_vertices.push(*vertex);
next_index += 1;
}
}
for index in &mut mesh.indices {
*index = compact_remap[*index as usize];
}
mesh.vertices = new_vertices;
Ok(())
}
}
impl Default for MeshMerger {
fn default() -> Self {
Self::new()
}
}
pub struct LodGenerator {
pub ratios: Vec<f32>,
}
impl LodGenerator {
pub fn new() -> Self {
Self {
ratios: vec![0.5, 0.25, 0.125], }
}
pub fn generate_lods(&self, mesh: &Mesh) -> Result<Vec<Mesh>> {
let mut lods = Vec::with_capacity(self.ratios.len() + 1);
lods.push(mesh.clone());
for &ratio in &self.ratios {
let simplified = self.simplify_mesh(&lods[0], ratio)?;
lods.push(simplified);
}
Ok(lods)
}
fn simplify_mesh(&self, mesh: &Mesh, ratio: f32) -> Result<Mesh> {
let target_triangles = ((mesh.indices.len() / 3) as f32 * ratio).max(1.0) as usize;
let mut simplified = Mesh::new();
simplified.material_id = mesh.material_id.clone();
let step = (mesh.indices.len() / 3).max(1) / target_triangles.max(1);
let step = step.max(1);
let mut vertex_map: HashMap<u32, u32> = HashMap::new();
for triangle_idx in (0..mesh.indices.len() / 3).step_by(step) {
let base = triangle_idx * 3;
for i in 0..3 {
let old_idx = mesh.indices[base + i];
if !vertex_map.contains_key(&old_idx) {
let new_idx = simplified.vertices.len() as u32;
vertex_map.insert(old_idx, new_idx);
simplified.vertices.push(mesh.vertices[old_idx as usize]);
}
simplified.indices.push(vertex_map[&old_idx]);
}
}
simplified.bounds = Aabb::EMPTY;
for vertex in &simplified.vertices {
simplified.bounds.expand_point(vertex.position);
}
Ok(simplified)
}
}
impl Default for LodGenerator {
fn default() -> Self {
Self::new()
}
}
pub struct Octree {
root: OctreeNode,
max_depth: usize,
max_elements: usize,
}
struct OctreeNode {
bounds: Aabb,
elements: Vec<usize>, children: Option<Box<[OctreeNode; 8]>>,
}
impl Octree {
pub fn new(bounds: Aabb) -> Self {
Self {
root: OctreeNode {
bounds,
elements: Vec::new(),
children: None,
},
max_depth: 8,
max_elements: 8,
}
}
pub fn insert(&mut self, mesh_index: usize, bounds: &Aabb) {
let max_depth = self.max_depth;
let max_elements = self.max_elements;
Self::insert_recursive_static(&mut self.root, mesh_index, bounds, 0, max_depth, max_elements);
}
fn insert_recursive_static(node: &mut OctreeNode, mesh_index: usize, bounds: &Aabb, depth: usize, max_depth: usize, max_elements: usize) {
if !node.bounds.intersects(bounds) {
return;
}
if let Some(ref mut children) = node.children {
for child in children.iter_mut() {
Self::insert_recursive_static(child, mesh_index, bounds, depth + 1, max_depth, max_elements);
}
return;
}
node.elements.push(mesh_index);
if node.elements.len() > max_elements && depth < max_depth {
Self::subdivide_node_static(node, max_depth, max_elements, depth);
}
}
fn subdivide_node_static(node: &mut OctreeNode, max_depth: usize, max_elements: usize, depth: usize) {
let _center = node.bounds.center();
let half_size = (node.bounds.max - node.bounds.min) * 0.5;
let mut children = Vec::with_capacity(8);
for i in 0..8 {
let offset = Vec3::new(
if i & 1 != 0 { half_size.x } else { 0.0 },
if i & 2 != 0 { half_size.y } else { 0.0 },
if i & 4 != 0 { half_size.z } else { 0.0 },
);
let min = node.bounds.min + offset;
let max = min + half_size;
children.push(OctreeNode {
bounds: Aabb::new(min, max),
elements: Vec::new(),
children: None,
});
}
node.children = Some(Box::new([
children.pop().unwrap(),
children.pop().unwrap(),
children.pop().unwrap(),
children.pop().unwrap(),
children.pop().unwrap(),
children.pop().unwrap(),
children.pop().unwrap(),
children.pop().unwrap(),
]));
}
pub fn query(&self, bounds: &Aabb) -> Vec<usize> {
let mut result = Vec::new();
self.query_recursive(&self.root, bounds, &mut result);
result
}
fn query_recursive(&self, node: &OctreeNode, bounds: &Aabb, result: &mut Vec<usize>) {
if !node.bounds.intersects(bounds) {
return;
}
result.extend(&node.elements);
if let Some(ref children) = node.children {
for child in children.iter() {
self.query_recursive(child, bounds, result);
}
}
}
}
pub struct Optimizer {
pub merger: MeshMerger,
pub lod_generator: LodGenerator,
}
impl Optimizer {
pub fn new() -> Self {
Self {
merger: MeshMerger::new(),
lod_generator: LodGenerator::new(),
}
}
pub fn optimize_scene(&self, scene: &Scene) -> Result<OptimizedScene> {
let merged_scene = self.merger.merge_scene(scene)?;
let mut lods_by_mesh = Vec::new();
for mesh in &merged_scene.meshes {
let lods = self.lod_generator.generate_lods(mesh)?;
lods_by_mesh.push(lods);
}
let bounds = merged_scene.bounds;
let mut octree = Octree::new(bounds);
for (i, mesh) in merged_scene.meshes.iter().enumerate() {
octree.insert(i, &mesh.bounds);
}
Ok(OptimizedScene {
base_scene: merged_scene,
lods: lods_by_mesh,
spatial_index: octree,
})
}
}
impl Default for Optimizer {
fn default() -> Self {
Self::new()
}
}
pub struct OptimizedScene {
pub base_scene: Scene,
pub lods: Vec<Vec<Mesh>>, pub spatial_index: Octree,
}
impl OptimizedScene {
pub fn select_lod(&self, mesh_index: usize, distance: f32) -> Option<&Mesh> {
if mesh_index >= self.lods.len() {
return None;
}
let lods = &self.lods[mesh_index];
if lods.is_empty() {
return None;
}
let lod_level = if distance < 10.0 {
0 } else if distance < 50.0 {
1.min(lods.len() - 1)
} else if distance < 100.0 {
2.min(lods.len() - 1)
} else {
3.min(lods.len() - 1)
};
Some(&lods[lod_level])
}
}
#[cfg(test)]
mod tests {
use super::*;
use avila_mesh::primitives;
#[test]
fn test_mesh_merger() {
let mut scene = Scene::new();
let cube1 = primitives::cube(1.0);
let cube2 = primitives::cube(1.0);
let cube3 = primitives::cube(1.0);
scene.add_mesh(cube1);
scene.add_mesh(cube2);
scene.add_mesh(cube3);
let merger = MeshMerger::new();
let merged_scene = merger.merge_scene(&scene).unwrap();
assert_eq!(merged_scene.meshes.len(), 1);
println!("Merged vertices: {}", merged_scene.meshes[0].vertices.len());
assert!(merged_scene.meshes[0].vertices.len() >= 8); assert!(merged_scene.meshes[0].indices.len() == 36 * 3); }
#[test]
fn test_lod_generation() {
let mesh = primitives::cube(2.0);
let original_triangles = mesh.indices.len() / 3;
let lod_gen = LodGenerator::new();
let lods = lod_gen.generate_lods(&mesh).unwrap();
assert_eq!(lods.len(), 4);
assert_eq!(lods[0].indices.len(), mesh.indices.len());
for i in 1..lods.len() {
let triangles = lods[i].indices.len() / 3;
assert!(triangles <= original_triangles);
}
}
#[test]
fn test_octree() {
let bounds = Aabb::new(Vec3::ZERO, Vec3::new(100.0, 100.0, 100.0));
let mut octree = Octree::new(bounds);
let mesh1_bounds = Aabb::new(Vec3::new(10.0, 10.0, 10.0), Vec3::new(20.0, 20.0, 20.0));
let mesh2_bounds = Aabb::new(Vec3::new(80.0, 80.0, 80.0), Vec3::new(90.0, 90.0, 90.0));
octree.insert(0, &mesh1_bounds);
octree.insert(1, &mesh2_bounds);
let query_bounds = Aabb::new(Vec3::ZERO, Vec3::new(30.0, 30.0, 30.0));
let results = octree.query(&query_bounds);
println!("Query results: {:?}", results);
assert!(results.contains(&0));
if results.len() == 1 {
assert!(!results.contains(&1));
}
}
#[test]
fn test_full_optimization() {
let mut scene = Scene::new();
scene.add_mesh(primitives::cube(1.0));
scene.add_mesh(primitives::cube(1.0));
let optimizer = Optimizer::new();
let optimized = optimizer.optimize_scene(&scene).unwrap();
assert_eq!(optimized.base_scene.meshes.len(), 1);
assert!(!optimized.lods.is_empty());
assert_eq!(optimized.lods[0].len(), 4); }
}