use crate::mesh::Mesh;
use glam::Vec3;
const OVERDRAW_CACHE_SIZE: usize = 32;
struct Cluster {
start_tri: usize,
end_tri: usize, centroid: Vec3,
}
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn optimize_overdraw(mesh: &mut Mesh, threshold: f32) {
let views = default_view_directions();
optimize_overdraw_with_views(mesh, threshold, &views);
}
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
pub fn optimize_overdraw_with_views(mesh: &mut Mesh, _threshold: f32, view_directions: &[Vec3]) {
let tri_count = mesh.indices.len() / 3;
if tri_count == 0 || view_directions.is_empty() {
return;
}
let clusters = compute_clusters(mesh, OVERDRAW_CACHE_SIZE);
if clusters.len() <= 1 {
return; }
let mut rank_sum = vec![0.0_f32; clusters.len()];
for &view in view_directions {
let mut view_indexed: Vec<(usize, f32)> = clusters
.iter()
.enumerate()
.map(|(i, c)| (i, c.centroid.dot(view)))
.collect();
view_indexed.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(core::cmp::Ordering::Equal));
for (rank, &(cluster_idx, _)) in view_indexed.iter().enumerate() {
rank_sum[cluster_idx] += rank as f32;
}
}
let mut cluster_order: Vec<usize> = (0..clusters.len()).collect();
cluster_order.sort_by(|&a, &b| {
rank_sum[a]
.partial_cmp(&rank_sum[b])
.unwrap_or(core::cmp::Ordering::Equal)
});
let mut new_indices = Vec::with_capacity(mesh.indices.len());
for &ci in &cluster_order {
let c = &clusters[ci];
for t in c.start_tri..c.end_tri {
let base = t * 3;
new_indices.push(mesh.indices[base]);
new_indices.push(mesh.indices[base + 1]);
new_indices.push(mesh.indices[base + 2]);
}
}
mesh.indices = new_indices;
}
#[must_use]
pub const fn default_view_directions() -> [Vec3; 6] {
[
Vec3::X,
Vec3::NEG_X,
Vec3::Y,
Vec3::NEG_Y,
Vec3::Z,
Vec3::NEG_Z,
]
}
#[allow(clippy::cast_precision_loss)]
fn compute_clusters(mesh: &Mesh, cache_size: usize) -> Vec<Cluster> {
let tri_count = mesh.indices.len() / 3;
if tri_count == 0 {
return Vec::new();
}
let mut clusters: Vec<Cluster> = Vec::new();
let mut cache: Vec<u32> = Vec::with_capacity(cache_size);
let mut miss_since_last_boundary = 0usize;
let mut cluster_start = 0usize;
let mut centroid_accum = Vec3::ZERO;
let mut centroid_count = 0usize;
let boundary_miss_count = 3usize;
for t in 0..tri_count {
let base = t * 3;
let ia = mesh.indices[base];
let ib = mesh.indices[base + 1];
let ic = mesh.indices[base + 2];
let mut misses = 0;
for &v in &[ia, ib, ic] {
if cache.contains(&v) {
} else {
misses += 1;
cache.push(v);
if cache.len() > cache_size {
cache.remove(0);
}
}
}
miss_since_last_boundary += misses;
if (ia as usize) < mesh.vertices.len() {
centroid_accum += mesh.vertices[ia as usize].position;
centroid_count += 1;
}
if (ib as usize) < mesh.vertices.len() {
centroid_accum += mesh.vertices[ib as usize].position;
centroid_count += 1;
}
if (ic as usize) < mesh.vertices.len() {
centroid_accum += mesh.vertices[ic as usize].position;
centroid_count += 1;
}
if misses >= boundary_miss_count && miss_since_last_boundary >= cache_size {
let centroid = if centroid_count > 0 {
centroid_accum / (centroid_count as f32)
} else {
Vec3::ZERO
};
clusters.push(Cluster {
start_tri: cluster_start,
end_tri: t + 1,
centroid,
});
cluster_start = t + 1;
miss_since_last_boundary = 0;
centroid_accum = Vec3::ZERO;
centroid_count = 0;
}
}
if cluster_start < tri_count {
let centroid = if centroid_count > 0 {
centroid_accum / (centroid_count as f32)
} else {
Vec3::ZERO
};
clusters.push(Cluster {
start_tri: cluster_start,
end_tri: tri_count,
centroid,
});
}
clusters
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mesh::{sdf_to_mesh, MarchingCubesConfig, Vertex};
use crate::types::SdfNode;
fn make_sphere_mesh(resolution: usize) -> Mesh {
let sphere = SdfNode::sphere(1.0);
sdf_to_mesh(
&sphere,
Vec3::splat(-2.0),
Vec3::splat(2.0),
&MarchingCubesConfig {
resolution,
iso_level: 0.0,
compute_normals: true,
..Default::default()
},
)
}
#[test]
fn test_overdraw_empty_mesh() {
let mut mesh = Mesh::new();
optimize_overdraw(&mut mesh, 1.0);
assert!(mesh.indices.is_empty());
}
#[test]
fn test_overdraw_preserves_triangle_count() {
let mut mesh = make_sphere_mesh(16);
let tri_before = mesh.triangle_count();
let vert_before = mesh.vertices.len();
optimize_overdraw(&mut mesh, 1.0);
assert_eq!(mesh.triangle_count(), tri_before);
assert_eq!(mesh.vertices.len(), vert_before);
}
#[test]
fn test_overdraw_preserves_triangle_set() {
let mut mesh = make_sphere_mesh(8);
let mut tris_before: Vec<[u32; 3]> = (0..mesh.triangle_count())
.map(|t| {
let mut tri = [
mesh.indices[t * 3],
mesh.indices[t * 3 + 1],
mesh.indices[t * 3 + 2],
];
tri.sort_unstable();
tri
})
.collect();
tris_before.sort_unstable();
optimize_overdraw(&mut mesh, 1.0);
let mut tris_after: Vec<[u32; 3]> = (0..mesh.triangle_count())
.map(|t| {
let mut tri = [
mesh.indices[t * 3],
mesh.indices[t * 3 + 1],
mesh.indices[t * 3 + 2],
];
tri.sort_unstable();
tri
})
.collect();
tris_after.sort_unstable();
assert_eq!(
tris_before, tris_after,
"三角形集合が保存されていない (index 追加/削除された?)"
);
}
#[test]
fn test_overdraw_with_custom_views() {
let mut mesh = make_sphere_mesh(8);
let tri_before = mesh.triangle_count();
let views = [Vec3::Y];
optimize_overdraw_with_views(&mut mesh, 1.0, &views);
assert_eq!(mesh.triangle_count(), tri_before);
}
#[test]
fn test_overdraw_single_cluster_noop() {
let v = |x: f32, z: f32| Vertex::new(Vec3::new(x, 0.0, z), Vec3::Y);
let mut mesh = Mesh {
vertices: vec![v(0.0, 0.0), v(1.0, 0.0), v(0.0, 1.0), v(1.0, 1.0)],
indices: vec![0, 1, 2, 1, 3, 2],
};
let indices_before = mesh.indices.clone();
optimize_overdraw(&mut mesh, 1.0);
assert_eq!(mesh.indices, indices_before);
}
#[test]
fn test_default_view_directions() {
let views = default_view_directions();
assert_eq!(views.len(), 6);
for v in &views {
assert!((v.length() - 1.0).abs() < 1e-6);
}
assert!(views.contains(&Vec3::X) && views.contains(&Vec3::NEG_X));
assert!(views.contains(&Vec3::Y) && views.contains(&Vec3::NEG_Y));
assert!(views.contains(&Vec3::Z) && views.contains(&Vec3::NEG_Z));
}
}