#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct UvVisConfig {
pub grid_density: f32,
pub show_grid: bool,
pub show_seams: bool,
pub enabled: bool,
pub line_width: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct UvEdge {
pub uv0: [f32; 2],
pub uv1: [f32; 2],
pub is_seam: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct UvIsland {
pub edges: Vec<UvEdge>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct UvVisDrawCall {
pub edges: Vec<UvEdge>,
pub islands: Vec<UvIsland>,
pub config: UvVisConfig,
}
#[allow(dead_code)]
pub fn default_uv_vis_config() -> UvVisConfig {
UvVisConfig {
grid_density: 8.0,
show_grid: true,
show_seams: true,
enabled: true,
line_width: 1.0,
}
}
#[allow(dead_code)]
pub fn build_uv_edges(uvs: &[[f32; 2]], faces: &[[u32; 3]]) -> Vec<UvEdge> {
use std::collections::HashSet;
let mut seen: HashSet<(u32, u32)> = HashSet::new();
let mut edges = Vec::new();
for face in faces {
let corners = [(face[0], face[1]), (face[1], face[2]), (face[2], face[0])];
for (a, b) in corners {
if a as usize >= uvs.len() || b as usize >= uvs.len() {
continue;
}
let key = if a < b { (a, b) } else { (b, a) };
if seen.insert(key) {
edges.push(UvEdge {
uv0: uvs[a as usize],
uv1: uvs[b as usize],
is_seam: false,
});
}
}
}
edges
}
#[allow(dead_code)]
pub fn detect_uv_seams(uvs: &[[f32; 2]], faces: &[[u32; 3]]) -> Vec<UvEdge> {
let mut seams = Vec::new();
for face in faces {
let corners = [(face[0], face[1]), (face[1], face[2]), (face[2], face[0])];
for (a, b) in corners {
if a as usize >= uvs.len() || b as usize >= uvs.len() {
continue;
}
let uv_a = uvs[a as usize];
let uv_b = uvs[b as usize];
let du = (uv_a[0] - uv_b[0]).abs();
let dv = (uv_a[1] - uv_b[1]).abs();
if du > 0.5 || dv > 0.5 {
seams.push(UvEdge {
uv0: uv_a,
uv1: uv_b,
is_seam: true,
});
}
}
}
seams
}
#[allow(dead_code)]
pub fn uv_vis_draw_call(
uvs: &[[f32; 2]],
faces: &[[u32; 3]],
cfg: &UvVisConfig,
) -> UvVisDrawCall {
let mut all_edges = build_uv_edges(uvs, faces);
let seams = detect_uv_seams(uvs, faces);
for seam in &seams {
for edge in &mut all_edges {
if edges_coincide(edge, seam) {
edge.is_seam = true;
}
}
}
let islands: Vec<UvIsland> = faces
.iter()
.filter_map(|face| {
let corners = [(face[0], face[1]), (face[1], face[2]), (face[2], face[0])];
let island_edges: Vec<UvEdge> = corners
.iter()
.filter_map(|&(a, b)| {
if a as usize >= uvs.len() || b as usize >= uvs.len() {
return None;
}
Some(UvEdge {
uv0: uvs[a as usize],
uv1: uvs[b as usize],
is_seam: false,
})
})
.collect();
if island_edges.is_empty() {
None
} else {
Some(UvIsland { edges: island_edges })
}
})
.collect();
UvVisDrawCall {
edges: all_edges,
islands,
config: cfg.clone(),
}
}
#[allow(dead_code)]
pub fn uv_edge_count(call: &UvVisDrawCall) -> usize {
call.edges.len()
}
#[allow(dead_code)]
pub fn set_uv_grid_density(cfg: &mut UvVisConfig, density: f32) {
cfg.grid_density = density;
}
#[allow(dead_code)]
pub fn uv_vis_toggle_grid(cfg: &mut UvVisConfig) {
cfg.show_grid = !cfg.show_grid;
}
#[allow(dead_code)]
pub fn uv_vis_toggle_seams(cfg: &mut UvVisConfig) {
cfg.show_seams = !cfg.show_seams;
}
#[allow(dead_code)]
pub fn uv_vis_is_enabled(cfg: &UvVisConfig) -> bool {
cfg.enabled
}
#[allow(dead_code)]
pub fn uv_island_count(call: &UvVisDrawCall) -> usize {
call.islands.len()
}
fn edges_coincide(a: &UvEdge, b: &UvEdge) -> bool {
(uv_eq(a.uv0, b.uv0) && uv_eq(a.uv1, b.uv1))
|| (uv_eq(a.uv0, b.uv1) && uv_eq(a.uv1, b.uv0))
}
fn uv_eq(a: [f32; 2], b: [f32; 2]) -> bool {
(a[0] - b[0]).abs() < 1e-6 && (a[1] - b[1]).abs() < 1e-6
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_uvs() -> Vec<[f32; 2]> {
vec![[0.0, 0.0], [1.0, 0.0], [0.5, 1.0]]
}
fn simple_faces() -> Vec<[u32; 3]> {
vec![[0, 1, 2]]
}
#[test]
fn default_config_enabled() {
let cfg = default_uv_vis_config();
assert!(uv_vis_is_enabled(&cfg));
assert!(cfg.show_grid);
assert!(cfg.show_seams);
}
#[test]
fn build_uv_edges_triangle() {
let uvs = simple_uvs();
let faces = simple_faces();
let edges = build_uv_edges(&uvs, &faces);
assert_eq!(edges.len(), 3);
}
#[test]
fn build_uv_edges_no_duplicates_shared_edge() {
let uvs = vec![[0.0, 0.0], [1.0, 0.0], [0.5, 1.0], [0.5, -1.0]];
let faces = vec![[0, 1, 2], [0, 3, 1]];
let edges = build_uv_edges(&uvs, &faces);
assert_eq!(edges.len(), 5);
}
#[test]
fn detect_uv_seams_no_seams() {
let uvs = vec![[0.1_f32, 0.1], [0.4, 0.1], [0.25, 0.4]];
let faces = vec![[0u32, 1, 2]];
let seams = detect_uv_seams(&uvs, &faces);
assert_eq!(seams.len(), 0);
}
#[test]
fn detect_uv_seams_with_boundary() {
let uvs = vec![[0.0_f32, 0.0], [1.0, 0.0], [0.5, 0.3]];
let faces = vec![[0u32, 1, 2]];
let seams = detect_uv_seams(&uvs, &faces);
assert!(!seams.is_empty());
}
#[test]
fn uv_vis_draw_call_island_count() {
let uvs = simple_uvs();
let faces = simple_faces();
let cfg = default_uv_vis_config();
let call = uv_vis_draw_call(&uvs, &faces, &cfg);
assert_eq!(uv_island_count(&call), 1);
}
#[test]
fn uv_edge_count_matches() {
let uvs = simple_uvs();
let faces = simple_faces();
let cfg = default_uv_vis_config();
let call = uv_vis_draw_call(&uvs, &faces, &cfg);
assert_eq!(uv_edge_count(&call), 3);
}
#[test]
fn toggle_grid_and_seams() {
let mut cfg = default_uv_vis_config();
uv_vis_toggle_grid(&mut cfg);
assert!(!cfg.show_grid);
uv_vis_toggle_seams(&mut cfg);
assert!(!cfg.show_seams);
}
#[test]
fn set_grid_density() {
let mut cfg = default_uv_vis_config();
set_uv_grid_density(&mut cfg, 16.0);
assert!((cfg.grid_density - 16.0).abs() < 1e-6);
}
}