diff --git a/obj/triangle_scaled.obj b/obj/triangle_scaled.obj new file mode 100644 index 0000000..085451d --- /dev/null +++ b/obj/triangle_scaled.obj @@ -0,0 +1,7 @@ +# A simple triangle object for testing +o Triangle +v 0 0 0 0.5 +v 1 0 0 0.5 +v 0 1 0 0.5 + +f -3 -2 -1 \ No newline at end of file diff --git a/obj/triangle_scaled_by_zero.obj b/obj/triangle_scaled_by_zero.obj new file mode 100644 index 0000000..d62961a --- /dev/null +++ b/obj/triangle_scaled_by_zero.obj @@ -0,0 +1,7 @@ +# A simple triangle object for testing +o Triangle +v 0 0 0 0.0 +v 1 0 0 0.5 +v 0 1 0 0.5 + +f -3 -2 -1 \ No newline at end of file diff --git a/obj/triangle_with_two_additional_floats.obj b/obj/triangle_with_two_additional_floats.obj new file mode 100644 index 0000000..f375fe8 --- /dev/null +++ b/obj/triangle_with_two_additional_floats.obj @@ -0,0 +1,7 @@ +# A simple triangle object for testing +o Triangle +v 0 0 0 0.5 1 +v 1 0 0 0.5 0 +v 0 1 0 0.5 1 + +f -3 -2 -1 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 05358f0..c559dba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -635,6 +635,8 @@ pub enum LoadError { ReadError, UnrecognizedCharacter, PositionParseError, + ScalingByZeroError, + ColorParseError, NormalParseError, TexcoordParseError, FaceParseError, @@ -656,6 +658,8 @@ impl fmt::Display for LoadError { LoadError::ReadError => "read error", LoadError::UnrecognizedCharacter => "unrecognized character", LoadError::PositionParseError => "position parse error", + LoadError::ScalingByZeroError => "scaling (w) by zero", + LoadError::ColorParseError => "color parse error (too many or too few components)", LoadError::NormalParseError => "normal parse error", LoadError::TexcoordParseError => "texcoord parse error", LoadError::FaceParseError => "face parse error", @@ -768,7 +772,7 @@ fn parse_floatn(val_str: &mut SplitWhitespace, vals: &mut Vec, n: usize) sz + n == vals.len() } -/// Parse the a string into a float3 array, returns an error if parsing failed +/// Parse a string into a float3 array, returns an error if parsing failed fn parse_float3(val_str: SplitWhitespace) -> Result<[Float; 3], LoadError> { let arr: [Float; 3] = val_str .take(3) @@ -780,7 +784,7 @@ fn parse_float3(val_str: SplitWhitespace) -> Result<[Float; 3], LoadError> { Ok(arr) } -/// Parse the a string into a float value, returns an error if parsing failed +/// Parse a string into a float value, returns an error if parsing failed fn parse_float(val_str: Option<&str>) -> Result { val_str .map(FromStr::from_str) @@ -1498,7 +1502,7 @@ fn reorder_data(mesh: &mut Mesh) { /// Merge identical points. A point has dimension N. #[cfg(feature = "merging")] #[inline] -fn merge_identical_points(points: &mut Vec, indices: &mut Vec) +fn merge_identical_points(points: &mut Vec, indices: &mut [u32]) where [(); size_of::<[Float; N]>()]:, { @@ -1703,12 +1707,51 @@ fn parse_obj_line( match words.next() { Some("#") | None => Ok(ParseReturnType::None), Some("v") => { + // we need three floats for the coordinates if !parse_floatn(&mut words, &mut models.pos, 3) { return Err(LoadError::PositionParseError); } - // Add inline vertex colors if present. - parse_floatn(&mut words, &mut models.v_color, 3); + // then it is possible to have either 0, 1, or 3 more float values + // 0 -> just coordinates + // 1 -> w value that scales x, y, and z parsed before + // 3 -> rgb values + // w and rgb values should not appear together + let Some(first) = words.next() else { + return Ok(ParseReturnType::None); + }; + + match (words.next(), words.next()) { + (None, _) => { + let w: Float = + FromStr::from_str(first).map_err(|_| LoadError::PositionParseError)?; + if w == 0.0 { + return Err(LoadError::ScalingByZeroError); + } + // apply this to the latest three coordinates + for coordinate in models.pos.iter_mut().rev().take(3) { + *coordinate /= w; + } + } + (Some(_), None) => { + // too few values + return Err(LoadError::ColorParseError); + } + (Some(second), Some(third)) => { + if words.next().is_some() { + // we have too many values + return Err(LoadError::ColorParseError); + } + + for rgb_component in [first, second, third] { + match FromStr::from_str(rgb_component) { + Ok(x) => models.v_color.push(x), + Err(_) => break, + } + } + } + } + Ok(ParseReturnType::None) } Some("vt") => { diff --git a/src/tests.rs b/src/tests.rs index 03126a0..f4a86af 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -48,6 +48,63 @@ fn simple_triangle() { assert!(mesh.vertex_color.is_empty()); } +#[test] +fn simple_triangle_scaled() { + let m = tobj::load_obj( + "obj/triangle_scaled.obj", + &tobj::LoadOptions { + single_index: true, + ..Default::default() + }, + ); + assert!(m.is_ok()); + let (models, mats) = m.unwrap(); + let mats = mats.unwrap(); + // We expect a single model with no materials + assert_eq!(models.len(), 1); + assert!(mats.is_empty()); + // Confirm our triangle is loaded correctly + assert_eq!(models[0].name, "Triangle"); + let mesh = &models[0].mesh; + assert!(mesh.normals.is_empty()); + assert!(mesh.texcoords.is_empty()); + assert_eq!(mesh.material_id, None); + + // Verify each position is loaded properly + let expect_pos = vec![0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0, 0.0]; + assert_float_eq!(mesh.positions, expect_pos, r2nd_all <= TOL); + // Verify the indices are loaded properly + let expect_idx = vec![0, 1, 2]; + assert_eq!(mesh.indices, expect_idx); + + // Verify that there are no vertex colors + assert!(mesh.vertex_color.is_empty()); +} + +#[test] +fn simple_triangle_scaled_by_zeor() { + let m = tobj::load_obj( + "obj/triangle_scaled_by_zero.obj", + &tobj::LoadOptions { + single_index: true, + ..Default::default() + }, + ); + assert!(m.is_err()); +} + +#[test] +fn triangle_with_two_floats() { + let m = tobj::load_obj( + "obj/triangle_with_two_additional_floats.obj", + &tobj::LoadOptions { + single_index: true, + ..Default::default() + }, + ); + assert!(m.is_err()); +} + #[test] fn simple_triangle_colored() { let m = tobj::load_obj(