#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct UvIslandBounds {
pub id: usize,
pub u_min: f32,
pub v_min: f32,
pub u_max: f32,
pub v_max: f32,
}
impl UvIslandBounds {
pub fn new(id: usize, u_min: f32, v_min: f32, u_max: f32, v_max: f32) -> Self {
Self {
id,
u_min,
v_min,
u_max,
v_max,
}
}
pub fn width(&self) -> f32 {
(self.u_max - self.u_min).max(0.0)
}
pub fn height(&self) -> f32 {
(self.v_max - self.v_min).max(0.0)
}
pub fn area(&self) -> f32 {
self.width() * self.height()
}
}
#[derive(Debug, Clone)]
pub struct PackedIsland {
pub id: usize,
pub u_offset: f32,
pub v_offset: f32,
}
pub fn pack_uv_islands(
islands: &[UvIslandBounds],
atlas_width: f32,
margin: f32,
) -> Vec<PackedIsland> {
let mut sorted: Vec<&UvIslandBounds> = islands.iter().collect();
sorted.sort_by(|a, b| {
b.height()
.partial_cmp(&a.height())
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut packed = Vec::new();
let mut shelf_x = 0.0_f32;
let mut shelf_y = 0.0_f32;
let mut shelf_h = 0.0_f32;
for island in sorted {
let w = island.width() + margin;
let h = island.height() + margin;
if shelf_x + w > atlas_width {
shelf_y += shelf_h;
shelf_x = 0.0;
shelf_h = 0.0;
}
packed.push(PackedIsland {
id: island.id,
u_offset: shelf_x,
v_offset: shelf_y,
});
shelf_x += w;
if h > shelf_h {
shelf_h = h;
}
}
packed
}
pub fn total_island_area(islands: &[UvIslandBounds]) -> f32 {
islands.iter().map(|i| i.area()).sum()
}
pub fn atlas_utilization(islands: &[UvIslandBounds], atlas_area: f32) -> f32 {
if atlas_area < 1e-8 {
return 0.0;
}
total_island_area(islands) / atlas_area
}
pub fn largest_island(islands: &[UvIslandBounds]) -> Option<usize> {
islands
.iter()
.enumerate()
.max_by(|a, b| {
a.1.area()
.partial_cmp(&b.1.area())
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_islands() -> Vec<UvIslandBounds> {
vec![
UvIslandBounds::new(0, 0.0, 0.0, 0.3, 0.3),
UvIslandBounds::new(1, 0.0, 0.0, 0.5, 0.2),
UvIslandBounds::new(2, 0.0, 0.0, 0.1, 0.5),
]
}
#[test]
fn test_island_width_height() {
let island = UvIslandBounds::new(0, 0.1, 0.2, 0.6, 0.9);
assert!((island.width() - 0.5).abs() < 1e-6);
assert!((island.height() - 0.7).abs() < 1e-6);
}
#[test]
fn test_island_area() {
let island = UvIslandBounds::new(0, 0.0, 0.0, 2.0, 3.0);
assert!((island.area() - 6.0).abs() < 1e-6);
}
#[test]
fn test_pack_count() {
let islands = sample_islands();
let packed = pack_uv_islands(&islands, 1.0, 0.01);
assert_eq!(packed.len(), islands.len());
}
#[test]
fn test_pack_offsets_non_negative() {
let islands = sample_islands();
let packed = pack_uv_islands(&islands, 1.0, 0.01);
for p in &packed {
assert!(p.u_offset >= 0.0);
assert!(p.v_offset >= 0.0);
}
}
#[test]
fn test_total_island_area() {
let islands = sample_islands();
let total = total_island_area(&islands);
assert!(total > 0.0);
}
#[test]
fn test_atlas_utilization_range() {
let islands = sample_islands();
let util = atlas_utilization(&islands, 1.0);
assert!((0.0..=1.0).contains(&util));
}
#[test]
fn test_largest_island() {
let islands = sample_islands();
let idx = largest_island(&islands).expect("should succeed");
assert_eq!(islands[idx].id, 1);
}
#[test]
fn test_largest_island_empty() {
assert!(largest_island(&[]).is_none());
}
}