mod utils;
use anyhow::Result;
use opensubdiv_petite::far::{
AdaptiveRefinementOptions, PatchTable, PatchTableOptions, PrimvarRefiner, TopologyDescriptor,
TopologyRefiner, TopologyRefinerOptions,
};
use std::fs::File;
use std::io::Write;
fn build_vertex_buffer(
refiner: &TopologyRefiner,
base_vertices: &[[f32; 3]],
) -> Result<Vec<[f32; 3]>> {
let primvar_refiner = PrimvarRefiner::new(refiner)?;
let total_vertices = refiner.vertex_count_all_levels();
println!("Building vertex buffer:");
println!(" Total vertices across all levels: {}", total_vertices);
println!(
" Number of refinement levels: {}",
refiner.refinement_levels()
);
let mut all_vertices = Vec::with_capacity(total_vertices);
println!(" Level 0: {} vertices", base_vertices.len());
all_vertices.extend_from_slice(base_vertices);
let num_levels = refiner.refinement_levels();
let mut level_start = 0;
for level in 1..num_levels {
let prev_level_count = refiner
.level(level - 1)
.map(|l| l.vertex_count())
.unwrap_or(0);
let level_verts = refiner.level(level).map(|l| l.vertex_count()).unwrap_or(0);
println!(
" Level {}: {} vertices (interpolating from {} vertices at level {})",
level,
level_verts,
prev_level_count,
level - 1
);
let src_data: Vec<f32> = all_vertices[level_start..level_start + prev_level_count]
.iter()
.flat_map(|v| v.iter().copied())
.collect();
if let Some(refined) = primvar_refiner.interpolate(level, 3, &src_data) {
let level_vertices: Vec<[f32; 3]> = refined
.chunks_exact(3)
.map(|chunk| [chunk[0], chunk[1], chunk[2]])
.collect();
println!(" Interpolated {} vertices", level_vertices.len());
all_vertices.extend_from_slice(&level_vertices);
}
level_start += prev_level_count;
}
println!(" Final vertex buffer size: {}", all_vertices.len());
Ok(all_vertices)
}
fn export_patch_cages_to_obj(
filename: &str,
patch_table: &PatchTable,
all_vertices: &[[f32; 3]],
) -> std::io::Result<()> {
let mut file = File::create(filename)?;
writeln!(file, "# OpenSubdiv Patch Control Cages")?;
writeln!(file, "# Number of patches: {}", patch_table.patch_count())?;
writeln!(file, "#")?;
let mut vertex_offset = 1; let mut patch_global_idx = 0;
for array_idx in 0..patch_table.patch_array_count() {
if let Some(patch_vertices) = patch_table.patch_array_vertices(array_idx) {
let num_patches = patch_table.patch_array_patch_count(array_idx);
for patch_idx in 0..num_patches {
writeln!(
file,
"# Patch {} (array {}, local {})",
patch_global_idx, array_idx, patch_idx
)?;
let start = patch_idx * 16;
for i in 0..16 {
let array_idx = start + i;
if array_idx < patch_vertices.len() {
let cv_idx = patch_vertices[array_idx].0 as usize;
if cv_idx < all_vertices.len() {
let v = &all_vertices[cv_idx];
writeln!(file, "v {} {} {}", v[0], v[1], v[2])?;
} else {
writeln!(file, "v 0 0 0 # ERROR: CV index {} out of bounds", cv_idx)?;
}
} else {
writeln!(file, "v 0 0 0 # ERROR: patch vertex index out of bounds")?;
}
}
for row in 0..3 {
for col in 0..3 {
let base = row * 4 + col;
let v1 = vertex_offset + base;
let v2 = vertex_offset + base + 1;
let v3 = vertex_offset + base + 5;
let v4 = vertex_offset + base + 4;
writeln!(file, "f {} {} {} {}", v1, v2, v3, v4)?;
}
}
writeln!(file)?; vertex_offset += 16;
patch_global_idx += 1;
}
}
}
Ok(())
}
#[test]
fn test_export_simple_plane_patches() -> Result<()> {
let mut vertex_positions = Vec::new();
for y in 0..4 {
for x in 0..4 {
vertex_positions.push([x as f32, y as f32, 0.0]);
}
}
let mut face_vertex_counts = Vec::new();
let mut face_vertex_indices = Vec::new();
for y in 0..3 {
for x in 0..3 {
face_vertex_counts.push(4);
let base = y * 4 + x;
face_vertex_indices.push(base);
face_vertex_indices.push(base + 1);
face_vertex_indices.push(base + 5);
face_vertex_indices.push(base + 4);
}
}
let descriptor = TopologyDescriptor::new(
vertex_positions.len(),
&face_vertex_counts,
&face_vertex_indices,
)?;
let refiner_options = TopologyRefinerOptions::default();
let mut refiner = TopologyRefiner::new(descriptor, refiner_options)
.expect("Failed to create topology refiner");
let adaptive_options = AdaptiveRefinementOptions {
isolation_level: 2,
..Default::default()
};
refiner.refine_adaptive(adaptive_options, &[]);
let patch_options =
PatchTableOptions::new().end_cap_type(opensubdiv_petite::far::EndCapType::BSplineBasis);
let patch_table =
PatchTable::new(&refiner, Some(patch_options)).expect("Failed to create patch table");
let all_vertices = build_vertex_buffer(&refiner, &vertex_positions)?;
let output_path = utils::test_output_path("simple_plane_patches.obj");
println!("Writing OBJ to: {:?}", output_path);
export_patch_cages_to_obj(output_path.to_str().unwrap(), &patch_table, &all_vertices)
.expect("Failed to export OBJ");
utils::assert_file_matches(&output_path, "simple_plane_patches.obj");
Ok(())
}
#[test]
fn test_export_simple_cube_patches() -> Result<()> {
let vertex_positions = vec![
[-0.5, -0.5, -0.5],
[0.5, -0.5, -0.5],
[-0.5, 0.5, -0.5],
[0.5, 0.5, -0.5],
[-0.5, 0.5, 0.5],
[0.5, 0.5, 0.5],
[-0.5, -0.5, 0.5],
[0.5, -0.5, 0.5],
];
let face_vertex_counts = vec![4, 4, 4, 4, 4, 4];
let face_vertex_indices = vec![
0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 0, 2, 4, 6, 1, 7, 5, 3, ];
let descriptor = TopologyDescriptor::new(
vertex_positions.len(),
&face_vertex_counts,
&face_vertex_indices,
)?;
let refiner_options = TopologyRefinerOptions::default();
let mut refiner = TopologyRefiner::new(descriptor, refiner_options)
.expect("Failed to create topology refiner");
let adaptive_options = AdaptiveRefinementOptions {
isolation_level: 2,
..Default::default()
};
refiner.refine_adaptive(adaptive_options, &[]);
let patch_options =
PatchTableOptions::new().end_cap_type(opensubdiv_petite::far::EndCapType::BSplineBasis);
let patch_table =
PatchTable::new(&refiner, Some(patch_options)).expect("Failed to create patch table");
let all_vertices = build_vertex_buffer(&refiner, &vertex_positions)?;
let output_path = utils::test_output_path("simple_cube_patches.obj");
println!("Writing OBJ to: {:?}", output_path);
export_patch_cages_to_obj(output_path.to_str().unwrap(), &patch_table, &all_vertices)
.expect("Failed to export OBJ");
utils::assert_file_matches(&output_path, "simple_cube_patches.obj");
Ok(())
}
#[test]
fn test_export_creased_cube_patches() -> Result<()> {
let vertex_positions = vec![
[-0.5, -0.5, 0.5],
[0.5, -0.5, 0.5],
[-0.5, 0.5, 0.5],
[0.5, 0.5, 0.5],
[-0.5, 0.5, -0.5],
[0.5, 0.5, -0.5],
[-0.5, -0.5, -0.5],
[0.5, -0.5, -0.5],
];
let face_vertex_counts = vec![4, 4, 4, 4, 4, 4];
let face_vertex_indices = vec![
0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 0, 2, 4, 6, 1, 7, 5, 3, ];
let crease_indices = vec![
0, 1, 1, 3, 3, 2, 2, 0, ];
let crease_weights = vec![2.0, 2.0, 2.0, 2.0];
let mut descriptor = TopologyDescriptor::new(
vertex_positions.len(),
&face_vertex_counts,
&face_vertex_indices,
)?;
descriptor.creases(&crease_indices, &crease_weights);
let refiner_options = TopologyRefinerOptions::default();
let mut refiner = TopologyRefiner::new(descriptor, refiner_options)
.expect("Failed to create topology refiner");
let adaptive_options = AdaptiveRefinementOptions {
isolation_level: 2,
..Default::default()
};
refiner.refine_adaptive(adaptive_options, &[]);
let patch_options =
PatchTableOptions::new().end_cap_type(opensubdiv_petite::far::EndCapType::BSplineBasis);
let patch_table =
PatchTable::new(&refiner, Some(patch_options)).expect("Failed to create patch table");
let all_vertices = build_vertex_buffer(&refiner, &vertex_positions)?;
let output_path = utils::test_output_path("creased_cube_patches.obj");
println!("Writing OBJ to: {:?}", output_path);
export_patch_cages_to_obj(output_path.to_str().unwrap(), &patch_table, &all_vertices)
.expect("Failed to export OBJ");
println!("Number of patches: {}", patch_table.patch_count());
utils::assert_file_matches(&output_path, "creased_cube_patches.obj");
Ok(())
}