use crate::far::{PatchTable, PatchType};
use std::io::{self, Write};
#[derive(Debug)]
pub enum ObjExportError {
UnsupportedPatchType(PatchType),
InvalidControlPoints,
Io(io::Error),
}
impl From<io::Error> for ObjExportError {
fn from(err: io::Error) -> Self {
ObjExportError::Io(err)
}
}
impl std::fmt::Display for ObjExportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedPatchType(t) => write!(f, "Unsupported patch type: {t:?}"),
Self::InvalidControlPoints => write!(f, "Invalid control point configuration"),
Self::Io(err) => write!(f, "IO error: {err}"),
}
}
}
impl std::error::Error for ObjExportError {}
pub type Result<T> = std::result::Result<T, ObjExportError>;
pub fn export_patches_as_bspline_surfaces<W: Write>(
writer: &mut W,
patch_table: &PatchTable,
control_points: &[[f32; 3]],
) -> Result<()> {
writeln!(writer, "# OpenSubdiv B-spline Surface Export")?;
writeln!(writer, "# Generated by opensubdiv-petite")?;
writeln!(writer, "# Number of patches: {}", patch_table.patch_count())?;
writeln!(writer, "#")?;
writeln!(writer, "# Control points")?;
control_points.iter().enumerate().try_for_each(|(i, cp)| {
writeln!(writer, "v {} {} {} # vertex {}", cp[0], cp[1], cp[2], i)
})?;
writeln!(writer)?;
let mut patch_global_idx = 0;
for array_idx in 0..patch_table.patch_array_count() {
if let Some(desc) = patch_table.patch_array_descriptor(array_idx) {
if desc.patch_type() != PatchType::Regular {
continue;
}
let num_patches = patch_table.patch_array_patch_count(array_idx);
if let Some(cv_indices) = patch_table.patch_array_vertices(array_idx) {
const REGULAR_PATCH_SIZE: usize = 16;
for patch_idx in 0..num_patches {
writeln!(
writer,
"# Patch {patch_global_idx} (array {array_idx}, local {patch_idx})"
)?;
writeln!(writer, "cstype bspline")?;
writeln!(writer, "deg 3 3")?;
let start = patch_idx * REGULAR_PATCH_SIZE;
let patch_cvs = &cv_indices[start..start + REGULAR_PATCH_SIZE];
write!(writer, "surf 0.0 1.0 0.0 1.0")?;
for cv in patch_cvs.iter().take(16) {
let cv_idx = cv.0 as usize;
if cv_idx >= control_points.len() {
return Err(ObjExportError::InvalidControlPoints);
}
write!(writer, " {}", cv_idx + 1)?;
}
writeln!(writer)?;
writeln!(writer, "parm u -3.0 -2.0 -1.0 0.0 1.0 2.0 3.0 4.0")?;
writeln!(writer, "parm v -3.0 -2.0 -1.0 0.0 1.0 2.0 3.0 4.0")?;
writeln!(writer, "end")?;
writeln!(writer)?;
patch_global_idx += 1;
}
}
}
}
Ok(())
}
pub trait PatchTableObjExt {
fn export_obj_bspline_surfaces<W: Write>(
&self,
writer: &mut W,
control_points: &[[f32; 3]],
) -> Result<()>;
fn export_obj_bspline_file(&self, path: &str, control_points: &[[f32; 3]]) -> Result<()>;
}
impl PatchTableObjExt for PatchTable {
fn export_obj_bspline_surfaces<W: Write>(
&self,
writer: &mut W,
control_points: &[[f32; 3]],
) -> Result<()> {
export_patches_as_bspline_surfaces(writer, self, control_points)
}
fn export_obj_bspline_file(&self, path: &str, control_points: &[[f32; 3]]) -> Result<()> {
let mut file = std::fs::File::create(path)?;
self.export_obj_bspline_surfaces(&mut file, control_points)
}
}