use lex::PeekableLexer;
use std::borrow::ToOwned;
use std::cmp::Ordering;
use std::cmp::Ordering::{Equal, Greater, Less};
use std::mem;
use lex::{Lexer, ParseError};
use util::OrderingExt;
#[derive(Clone, Debug, PartialEq)]
pub struct ObjSet {
pub material_library: Option<String>,
pub objects: Vec<Object>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Object {
pub name: String,
pub vertices: Vec<Vertex>,
pub tex_vertices: Vec<TVertex>,
pub normals: Vec<Normal>,
pub geometry: Vec<Geometry>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Geometry {
pub material_name: Option<String>,
pub shapes: Vec<Shape>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Shape {
pub primitive: Primitive,
pub groups: Vec<GroupName>,
pub smoothing_groups: Vec<u32>,
}
pub type GroupName = String;
#[derive(Clone, Copy, Debug, Hash, PartialEq)]
pub enum Primitive {
Point(VTNIndex),
Line(VTNIndex, VTNIndex),
Triangle(VTNIndex, VTNIndex, VTNIndex),
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub struct Vertex {
pub x: f64,
pub y: f64,
pub z: f64,
}
pub type Normal = Vertex;
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub struct TVertex {
pub u: f64,
pub v: f64,
pub w: f64,
}
fn fuzzy_cmp(a: f64, b: f64, delta: f64) -> Ordering {
if (a - b).abs() <= delta {
Equal
} else if a < b {
Less
} else {
Greater
}
}
impl PartialEq for Vertex {
fn eq(&self, other: &Vertex) -> bool {
self.partial_cmp(other).unwrap() == Equal
}
}
impl PartialOrd for Vertex {
fn partial_cmp(&self, other: &Vertex) -> Option<Ordering> {
Some(
fuzzy_cmp(self.x, other.x, 0.00001)
.lexico(|| fuzzy_cmp(self.y, other.y, 0.00001))
.lexico(|| fuzzy_cmp(self.z, other.z, 0.00001)),
)
}
}
impl PartialEq for TVertex {
fn eq(&self, other: &TVertex) -> bool {
self.partial_cmp(other).unwrap() == Equal
}
}
impl PartialOrd for TVertex {
fn partial_cmp(&self, other: &TVertex) -> Option<Ordering> {
Some(
fuzzy_cmp(self.u, other.u, 0.00001)
.lexico(|| fuzzy_cmp(self.v, other.v, 0.00001))
.lexico(|| fuzzy_cmp(self.w, other.w, 0.00001)),
)
}
}
pub type VertexIndex = usize;
pub type TextureIndex = usize;
pub type NormalIndex = usize;
pub type VTNIndex = (VertexIndex, Option<TextureIndex>, Option<NormalIndex>);
fn to_triangles(xs: &[VTNIndex]) -> Vec<Primitive> {
match xs.len() {
0 => return vec![],
1 => return vec![Primitive::Point(xs[0])],
2 => return vec![Primitive::Line(xs[0], xs[1])],
_ => {}
}
let last_elem = *xs.last().unwrap();
xs[..xs.len() - 1]
.iter()
.zip(xs[1..xs.len() - 1].iter())
.map(|(&x, &y)| Primitive::Triangle(last_elem, x, y))
.collect()
}
#[test]
fn test_to_triangles() {
use self::Primitive::{Line, Point, Triangle};
assert_eq!(to_triangles(&[]), vec!());
assert_eq!(
to_triangles(&[(3, None, None)]),
vec!(Point((3, None, None)))
);
assert_eq!(
to_triangles(&[(1, None, None), (2, None, None)]),
vec!(Line((1, None, None), (2, None, None)))
);
assert_eq!(
to_triangles(&[(1, None, None), (2, None, None), (3, None, None)]),
vec!(Triangle((3, None, None), (1, None, None), (2, None, None)))
);
assert_eq!(
to_triangles(&[
(1, None, None),
(2, None, None),
(3, None, None),
(4, None, None)
]),
vec!(
Triangle((4, None, None), (1, None, None), (2, None, None)),
Triangle((4, None, None), (2, None, None), (3, None, None))
)
);
assert_eq!(
to_triangles(&[
(1, None, None),
(2, None, None),
(3, None, None),
(4, None, None),
(5, None, None)
]),
vec!(
Triangle((5, None, None), (1, None, None), (2, None, None)),
Triangle((5, None, None), (2, None, None), (3, None, None)),
Triangle((5, None, None), (3, None, None), (4, None, None))
)
);
}
#[derive(Clone)]
struct Parser<'a> {
line_number: usize,
lexer: PeekableLexer<'a>,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Parser<'a> {
Parser {
line_number: 1,
lexer: PeekableLexer::new(Lexer::new(input)),
}
}
fn error_raw(&self, msg: String) -> ParseError {
ParseError {
line_number: self.line_number,
message: msg,
}
}
fn error<A, E>(&self, msg: E) -> Result<A, ParseError>
where
E: Into<String>,
{
Err(self.error_raw(msg.into()))
}
fn next(&mut self) -> Option<&'a str> {
let ret = self.lexer.next_str();
if let Some("\n") = ret {
self.line_number += 1;
}
ret
}
fn advance(&mut self) {
self.next();
}
fn peek(&mut self) -> Option<&'a str> {
self.lexer.peek_str()
}
fn try<P, T>(&mut self, parse: P) -> Option<T>
where
P: FnOnce(&mut Self) -> Result<T, ParseError>,
{
let mut tried = self.clone();
match parse(&mut tried) {
Ok(r) => {
*self = tried;
Some(r)
}
Err(_) => None,
}
}
fn bytes_consumed(&self, checkpoint: &Self) -> Option<usize> {
self.lexer.bytes_consumed(&checkpoint.lexer)
}
fn zero_or_more_newlines(&mut self) {
while let Some("\n") = self.peek() {
self.advance()
}
}
fn parse_tag(&mut self, tag: &str) -> Result<(), ParseError> {
match self.next() {
None => self.error(format!("Expected `{}` but got end of input.", tag)),
Some(s) if s != tag => self.error(format!("Expected `{}` but got {}.", tag, s)),
_ => Ok(()),
}
}
fn parse_tag_or_eof(&mut self, tag: &str) -> Result<(), ParseError> {
match self.next() {
Some(s) if s != tag => self.error(format!("Expected `{}` or EOF but got {}.", tag, s)),
_ => Ok(()),
}
}
fn one_or_more_newlines(&mut self) -> Result<(), ParseError> {
self.parse_tag_or_eof("\n")?;
self.zero_or_more_newlines();
Ok(())
}
fn parse_str(&mut self) -> Result<&'a str, ParseError> {
match self.next() {
None => self.error(format!("Expected string but got end of input.")),
Some("\n") => self.error(format!("Expected string but got `end of line`.")),
Some(got) => Ok(got),
}
}
fn parse_material_library(&mut self) -> Result<Option<&'a str>, ParseError> {
match self.peek() {
Some("mtllib") => {}
_ => return Ok(None),
}
self.advance();
self.parse_str().map(Some)
}
fn parse_object_name(&mut self) -> Result<&'a str, ParseError> {
match self.peek() {
Some("o") => {
self.parse_tag("o")?;
let name = self.parse_str();
self.one_or_more_newlines()?;
name
}
_ => Ok(""),
}
}
fn parse_double(&mut self) -> Result<f64, ParseError> {
let s = self.parse_str()?;
lexical::try_parse(s).map_err(|_| self.error_raw(format!("Expected f64 but got {}.", s)))
}
fn parse_vertex(&mut self) -> Result<Vertex, ParseError> {
self.parse_tag("v")?;
let x = self.parse_double()?;
let y = self.parse_double()?;
let z = self.parse_double()?;
Ok(Vertex { x, y, z })
}
fn parse_tex_vertex(&mut self) -> Result<TVertex, ParseError> {
self.parse_tag("vt")?;
let u = self.parse_double()?;
match self.try(Self::parse_double) {
Some(v) => {
let w = self.try(Self::parse_double).unwrap_or(0.);
Ok(TVertex { u, v, w })
}
None => Ok(TVertex { u, v: 0., w: 0. }),
}
}
fn parse_normal(&mut self) -> Result<Normal, ParseError> {
self.parse_tag("vn")?;
let x = self.parse_double()?;
let y = self.parse_double()?;
let z = self.parse_double()?;
Ok(Normal { x, y, z })
}
fn parse_usemtl(&mut self) -> Result<&'a str, ParseError> {
self.parse_tag("usemtl")?;
self.parse_str()
}
#[inline]
fn parse_isize_from(&self, s: &str) -> Result<isize, ParseError> {
lexical::try_parse(&s).map_err(|_| self.error_raw(format!("Expected isize but got {}.", s)))
}
fn parse_u32(&mut self) -> Result<u32, ParseError> {
let s = self.parse_str()?;
lexical::try_parse(&s).map_err(|_| self.error_raw(format!("Expected u32 but got {}.", s)))
}
fn parse_vtindex(
&mut self,
valid_vtx: (usize, usize),
valid_tx: (usize, usize),
valid_nx: (usize, usize),
) -> Result<VTNIndex, ParseError> {
match self.next() {
None => return self.error("Expected vertex index but got end of input.".to_owned()),
Some(s) => {
let process_split =
|split: &str, valid_range: (usize, usize)| -> Result<Option<usize>, ParseError> {
if split.len() > 0 {
Ok(Some(self.check_valid_index(
valid_range,
self.parse_isize_from(split)?,
)?))
} else {
Ok(None)
}
};
let mut splits_iter = s.split('/');
let split1 = splits_iter
.next()
.and_then(|s| process_split(&s, valid_vtx).transpose())
.transpose()?;
let split2 = splits_iter
.next()
.and_then(|s| process_split(&s, valid_tx).transpose())
.transpose()?;
let split3 = splits_iter
.next()
.and_then(|s| process_split(&s, valid_nx).transpose())
.transpose()?;
if split1.is_none() || splits_iter.next().is_some() {
self.error(format!("Expected at least 1 and at most 3 vertex indexes."))
} else {
Ok((split1.unwrap(), split2, split3))
}
}
}
}
#[inline(always)]
fn check_valid_index(
&self,
valid_values: (usize, usize),
actual_value: isize,
) -> Result<usize, ParseError> {
let (min, max) = valid_values;
let mut x = actual_value;
if x < 0 {
x = max as isize - x;
}
if x >= min as isize && x < max as isize {
debug_assert!(x > 0);
Ok((x - min as isize) as usize)
} else {
self.error(format!(
"Expected index in the range [{}, {}), but got {}.",
min, max, actual_value
))
}
}
fn parse_face(
&mut self,
valid_vtx: (usize, usize),
valid_tx: (usize, usize),
valid_nx: (usize, usize),
current_groups: &Vec<GroupName>,
current_smoothing_groups: &Vec<u32>,
) -> Result<Vec<Shape>, ParseError> {
match self.next() {
Some("f") => {}
Some("l") => {}
None => return self.error("Expected `f` or `l` but got end of input.".to_owned()),
Some(s) => return self.error(format!("Expected `f` or `l` but got {}.", s)),
}
let mut corner_list = Vec::new();
corner_list.push(self.parse_vtindex(valid_vtx, valid_tx, valid_nx)?);
loop {
match self.peek() {
None => break,
Some("\n") => break,
Some(_) => corner_list.push(self.parse_vtindex(valid_vtx, valid_tx, valid_nx)?),
}
}
Ok(
to_triangles(&corner_list)
.into_iter()
.map(|prim| Shape {
primitive: prim,
groups: current_groups.clone(),
smoothing_groups: current_smoothing_groups.clone(),
})
.collect(),
)
}
fn parse_geometries(
&mut self,
valid_vtx: (usize, usize),
valid_tx: (usize, usize),
valid_nx: (usize, usize),
) -> Result<Vec<Geometry>, ParseError> {
let mut result = Vec::new();
let mut shapes = Vec::new();
let mut current_material = None;
let mut current_groups = Vec::new();
let mut current_smoothing_groups = Vec::new();
loop {
match self.peek() {
Some("usemtl") => {
let old_material = mem::replace(&mut current_material, Some(self.parse_usemtl()?));
result.push(Geometry {
material_name: old_material.map(|s| s.to_owned()),
shapes: mem::replace(&mut shapes, Vec::new()),
});
}
Some("s") => {
self.advance();
current_smoothing_groups = self.parse_smoothing_groups()?;
}
Some("f") | Some("l") => {
shapes.extend(
self
.parse_face(
valid_vtx,
valid_tx,
valid_nx,
¤t_groups,
¤t_smoothing_groups,
)?
.into_iter(),
);
}
Some("g") => {
self.advance();
let names = self.parse_groups()?;
current_groups = names;
}
_ => break,
}
self.one_or_more_newlines()?;
}
result.push(Geometry {
material_name: current_material.map(|s| s.to_owned()),
shapes,
});
Ok(
result
.into_iter()
.filter(|ref x| !x.shapes.is_empty())
.collect(),
)
}
fn parse_object(
&mut self,
min_vertex_index: &mut usize,
max_vertex_index: &mut usize,
min_tex_index: &mut usize,
max_tex_index: &mut usize,
min_normal_index: &mut usize,
max_normal_index: &mut usize,
) -> Result<Object, ParseError> {
let name = self.parse_object_name()?;
let mut vertices = Vec::new();
let mut normals = Vec::new();
let mut tex_vertices = Vec::new();
loop {
if let Some(v) = self.try(Self::parse_vertex) {
vertices.push(v);
} else if let Some(vn) = self.try(Self::parse_normal) {
normals.push(vn);
} else if let Some(vt) = self.try(Self::parse_tex_vertex) {
tex_vertices.push(vt);
} else {
break;
}
self.one_or_more_newlines()?;
}
*max_vertex_index += vertices.len();
*max_tex_index += tex_vertices.len();
*max_normal_index += normals.len();
let geometry = self.parse_geometries(
(*min_vertex_index, *max_vertex_index),
(*min_tex_index, *max_tex_index),
(*min_normal_index, *max_normal_index),
)?;
*min_vertex_index += vertices.len();
*min_tex_index += tex_vertices.len();
*min_normal_index += normals.len();
Ok(Object {
name: name.to_owned(),
vertices,
tex_vertices,
normals,
geometry,
})
}
fn parse_objects(&mut self) -> Result<Vec<Object>, ParseError> {
let mut result = Vec::new();
let mut min_vertex_index = 1;
let mut max_vertex_index = 1;
let mut min_tex_index = 1;
let mut max_tex_index = 1;
let mut min_normal_index = 1;
let mut max_normal_index = 1;
loop {
match self.peek() {
Some(_) => {
let checkpoint = self.clone();
result.push(self.parse_object(
&mut min_vertex_index,
&mut max_vertex_index,
&mut min_tex_index,
&mut max_tex_index,
&mut min_normal_index,
&mut max_normal_index,
)?);
if self.bytes_consumed(&checkpoint).unwrap_or(0) == 0 {
return self.error("cannot parse corrupted data");
}
}
None => break,
}
self.zero_or_more_newlines();
}
Ok(result)
}
fn parse_objset(&mut self) -> Result<ObjSet, ParseError> {
self.zero_or_more_newlines();
let material_library = self.parse_material_library()?;
if material_library.is_some() {
self.one_or_more_newlines()?;
}
let objects = self.parse_objects()?;
self.zero_or_more_newlines();
if let Some(s) = self.peek() {
return self.error(format!("Expected end of input but got {}.", s));
}
Ok(ObjSet {
material_library: material_library.map(|s| s.to_owned()),
objects,
})
}
fn parse_groups(&mut self) -> Result<Vec<GroupName>, ParseError> {
let mut groups = Vec::new();
loop {
if let Some("\n") = self.peek() {
break;
}
let name = self.parse_str()?;
groups.push(name.to_owned());
}
Ok(groups)
}
fn parse_smoothing_groups(&mut self) -> Result<Vec<u32>, ParseError> {
let mut groups = Vec::new();
if self.try(|p| p.parse_tag("off")).is_none() {
loop {
let group = self.parse_u32()?;
groups.push(group);
if let Some("\n") = self.peek() {
break;
}
}
}
Ok(groups)
}
}
#[test]
fn test_parse() {
use self::Primitive::{Line, Triangle};
let test_case = r#"
# Blender v2.69 (sub 0) OBJ File: ''
# www.blender.org
mtllib untitled.mtl
o Cube.001
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 1.000000 -1.000000
v 1.000000 1.000000 1.000000
usemtl None
s off
f 5 6 2 1
f 6 7 3 2
f 7 8 4 3
f 8 5 1 4
f 1 2 3 4
f 8 7 6 5
o Circle
v 0.000000 0.000000 -1.000000
v -0.195090 0.000000 -0.980785
v -0.382683 0.000000 -0.923880
v -0.555570 0.000000 -0.831470
v -0.707107 0.000000 -0.707107
v -0.831470 0.000000 -0.555570
v -0.923880 0.000000 -0.382683
v -0.980785 0.000000 -0.195090
v -1.000000 0.000000 -0.000000
v -0.980785 0.000000 0.195090
v -0.923880 0.000000 0.382683
v -0.831470 0.000000 0.555570
v -0.707107 0.000000 0.707107
v -0.555570 0.000000 0.831470
v -0.382683 0.000000 0.923880
v -0.195090 0.000000 0.980785
v 0.000000 0.000000 1.000000
v 0.195091 0.000000 0.980785
v 0.382684 0.000000 0.923879
v 0.555571 0.000000 0.831469
v 0.707107 0.000000 0.707106
v 0.831470 0.000000 0.555570
v 0.923880 0.000000 0.382683
v 0.980785 0.000000 0.195089
v 1.000000 0.000000 -0.000001
v 0.980785 0.000000 -0.195091
v 0.923879 0.000000 -0.382684
v 0.831469 0.000000 -0.555571
v 0.707106 0.000000 -0.707108
v 0.555569 0.000000 -0.831470
v 0.382682 0.000000 -0.923880
v 0.195089 0.000000 -0.980786
l 10 9
l 11 10
l 12 11
l 13 12
l 14 13
l 15 14
l 16 15
l 17 16
l 18 17
l 19 18
l 20 19
l 21 20
l 22 21
l 23 22
l 24 23
l 25 24
l 26 25
l 27 26
l 28 27
l 29 28
l 30 29
l 31 30
l 32 31
l 33 32
l 34 33
l 35 34
l 36 35
l 37 36
l 38 37
l 39 38
l 40 39
l 9 40
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
usemtl Material
s off
f 41 42 43 44
f 45 48 47 46
f 41 45 46 42
f 42 46 47 43
f 43 47 48 44
f 45 41 44 48"#;
let expected = Ok(ObjSet {
material_library: Some("untitled.mtl".to_owned()),
objects: vec![
Object {
name: "Cube.001".to_owned(),
vertices: vec![
(-1, -1, 1),
(-1, -1, -1),
(1, -1, -1),
(1, -1, 1),
(-1, 1, 1),
(-1, 1, -1),
(1, 1, -1),
(1, 1, 1),
]
.into_iter()
.map(|(x, y, z)| Vertex {
x: x as f64,
y: y as f64,
z: z as f64,
})
.collect(),
tex_vertices: vec![],
normals: vec![],
geometry: vec![Geometry {
material_name: Some("None".to_owned()),
shapes: vec![
(0, 4, 5),
(0, 5, 1),
(1, 5, 6),
(1, 6, 2),
(2, 6, 7),
(2, 7, 3),
(3, 7, 4),
(3, 4, 0),
(3, 0, 1),
(3, 1, 2),
(4, 7, 6),
(4, 6, 5),
]
.into_iter()
.map(|(x, y, z)| Shape {
primitive: Triangle((x, None, None), (y, None, None), (z, None, None)),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
},
Object {
name: "Circle".to_owned(),
vertices: vec![
(0.0, 0.0, -1.0),
(-0.19509, 0.0, -0.980785),
(-0.382683, 0.0, -0.92388),
(-0.55557, 0.0, -0.83147),
(-0.707107, 0.0, -0.707107),
(-0.83147, 0.0, -0.55557),
(-0.92388, 0.0, -0.382683),
(-0.980785, 0.0, -0.19509),
(-1.0, 0.0, 0.0),
(-0.980785, 0.0, 0.19509),
(-0.92388, 0.0, 0.382683),
(-0.83147, 0.0, 0.55557),
(-0.707107, 0.0, 0.707107),
(-0.55557, 0.0, 0.83147),
(-0.382683, 0.0, 0.92388),
(-0.19509, 0.0, 0.980785),
(0.0, 0.0, 1.0),
(0.195091, 0.0, 0.980785),
(0.382684, 0.0, 0.923879),
(0.555571, 0.0, 0.831469),
(0.707107, 0.0, 0.707106),
(0.83147, 0.0, 0.55557),
(0.92388, 0.0, 0.382683),
(0.980785, 0.0, 0.195089),
(1.0, 0.0, -0.000001),
(0.980785, 0.0, -0.195091),
(0.923879, 0.0, -0.382684),
(0.831469, 0.0, -0.555571),
(0.707106, 0.0, -0.707108),
(0.555569, 0.0, -0.83147),
(0.382682, 0.0, -0.92388),
(0.195089, 0.0, -0.980786),
]
.into_iter()
.map(|(x, y, z)| Vertex { x, y, z })
.collect(),
tex_vertices: vec![],
normals: vec![],
geometry: vec![Geometry {
material_name: None,
shapes: vec![
(1, 0),
(2, 1),
(3, 2),
(4, 3),
(5, 4),
(6, 5),
(7, 6),
(8, 7),
(9, 8),
(10, 9),
(11, 10),
(12, 11),
(13, 12),
(14, 13),
(15, 14),
(16, 15),
(17, 16),
(18, 17),
(19, 18),
(20, 19),
(21, 20),
(22, 21),
(23, 22),
(24, 23),
(25, 24),
(26, 25),
(27, 26),
(28, 27),
(29, 28),
(30, 29),
(31, 30),
(0, 31),
]
.into_iter()
.map(|(x, y)| Shape {
primitive: Line((x, None, None), (y, None, None)),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
},
Object {
name: "Cube".to_owned(),
vertices: vec![
(1.0, -1.0, -1.0),
(1.0, -1.0, 1.0),
(-1.0, -1.0, 1.0),
(-1.0, -1.0, -1.0),
(1.0, 1.0, -0.999999),
(0.999999, 1.0, 1.000001),
(-1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0),
]
.into_iter()
.map(|(x, y, z)| Vertex { x, y, z })
.collect(),
tex_vertices: vec![],
normals: vec![],
geometry: vec![Geometry {
material_name: Some("Material".to_owned()),
shapes: vec![
(3, 0, 1),
(3, 1, 2),
(5, 4, 7),
(5, 7, 6),
(1, 0, 4),
(1, 4, 5),
(2, 1, 5),
(2, 5, 6),
(3, 2, 6),
(3, 6, 7),
(7, 4, 0),
(7, 0, 3),
]
.into_iter()
.map(|(x, y, z)| Shape {
primitive: Triangle((x, None, None), (y, None, None), (z, None, None)),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
},
],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn test_cube() {
use self::Primitive::Triangle;
let test_case = r#"
# Blender v2.71 (sub 0) OBJ File: 'cube.blend'
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.004952 0.498633
vt 0.754996 0.498236
vt 0.755393 0.248279
vt 1.005349 0.248677
vt 0.255083 0.497442
vt 0.255480 0.247485
vt 0.505437 0.247882
vt 0.505039 0.497839
vt 0.754598 0.748193
vt 0.504642 0.747795
vt 0.505834 -0.002074
vt 0.755790 -0.001677
vt 0.005127 0.497044
vt 0.005524 0.247088
usemtl Material
s off
f 1/1 2/2 3/3 4/4
f 5/5 8/6 7/7 6/8
f 1/9 5/10 6/8 2/2
f 2/2 6/8 7/7 3/3
f 3/3 7/7 8/11 4/12
f 5/5 1/13 4/14 8/6"#;
let expected = Ok(ObjSet {
material_library: Some("cube.mtl".to_owned()),
objects: vec![Object {
name: "Cube".to_owned(),
vertices: vec![
(1.0, -1.0, -1.0),
(1.0, -1.0, 1.0),
(-1.0, -1.0, 1.0),
(-1.0, -1.0, -1.0),
(1.0, 1.0, -1.0),
(1.0, 1.0, 1.0),
(-1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0),
]
.into_iter()
.map(|(x, y, z)| Vertex {
x: x as f64,
y: y as f64,
z: z as f64,
})
.collect(),
tex_vertices: vec![
(1.004952, 0.498633),
(0.754996, 0.498236),
(0.755393, 0.248279),
(1.005349, 0.248677),
(0.255083, 0.497442),
(0.25548, 0.247485),
(0.505437, 0.247882),
(0.505039, 0.497839),
(0.754598, 0.748193),
(0.504642, 0.747795),
(0.505834, -0.002074),
(0.75579, -0.001677),
(0.005127, 0.497044),
(0.005524, 0.247088),
]
.into_iter()
.map(|(u, v)| TVertex { u, v, w: 0. })
.collect(),
normals: vec![],
geometry: vec![Geometry {
material_name: Some("Material".to_owned()),
shapes: vec![
(3, 3, 0, 0, 1, 1),
(3, 3, 1, 1, 2, 2),
(5, 7, 4, 4, 7, 5),
(5, 7, 7, 5, 6, 6),
(1, 1, 0, 8, 4, 9),
(1, 1, 4, 9, 5, 7),
(2, 2, 1, 1, 5, 7),
(2, 2, 5, 7, 6, 6),
(3, 11, 2, 2, 6, 6),
(3, 11, 6, 6, 7, 10),
(7, 5, 4, 4, 0, 12),
(7, 5, 0, 12, 3, 13),
]
.into_iter()
.map(|(vx, tx, vy, ty, vz, tz)| Shape {
primitive: Triangle(
(vx, Some(tx), None),
(vy, Some(ty), None),
(vz, Some(tz), None),
),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
}],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn test_cube_anonymous_object() {
use self::Primitive::Triangle;
let test_case = r#"
# Blender v2.71 (sub 0) OBJ File: 'cube.blend'
# www.blender.org
mtllib cube.mtl
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.004952 0.498633
vt 0.754996 0.498236
vt 0.755393 0.248279
vt 1.005349 0.248677
vt 0.255083 0.497442
vt 0.255480 0.247485
vt 0.505437 0.247882
vt 0.505039 0.497839
vt 0.754598 0.748193
vt 0.504642 0.747795
vt 0.505834 -0.002074
vt 0.755790 -0.001677
vt 0.005127 0.497044
vt 0.005524 0.247088
usemtl Material
s off
f 1/1 2/2 3/3 4/4
f 5/5 8/6 7/7 6/8
f 1/9 5/10 6/8 2/2
f 2/2 6/8 7/7 3/3
f 3/3 7/7 8/11 4/12
f 5/5 1/13 4/14 8/6"#;
let expected = Ok(ObjSet {
material_library: Some("cube.mtl".to_owned()),
objects: vec![Object {
name: String::new(),
vertices: vec![
(1.0, -1.0, -1.0),
(1.0, -1.0, 1.0),
(-1.0, -1.0, 1.0),
(-1.0, -1.0, -1.0),
(1.0, 1.0, -1.0),
(1.0, 1.0, 1.0),
(-1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0),
]
.into_iter()
.map(|(x, y, z)| Vertex {
x: x as f64,
y: y as f64,
z: z as f64,
})
.collect(),
tex_vertices: vec![
(1.004952, 0.498633),
(0.754996, 0.498236),
(0.755393, 0.248279),
(1.005349, 0.248677),
(0.255083, 0.497442),
(0.25548, 0.247485),
(0.505437, 0.247882),
(0.505039, 0.497839),
(0.754598, 0.748193),
(0.504642, 0.747795),
(0.505834, -0.002074),
(0.75579, -0.001677),
(0.005127, 0.497044),
(0.005524, 0.247088),
]
.into_iter()
.map(|(u, v)| TVertex { u, v, w: 0. })
.collect(),
normals: vec![],
geometry: vec![Geometry {
material_name: Some("Material".to_owned()),
shapes: vec![
(3, 3, 0, 0, 1, 1),
(3, 3, 1, 1, 2, 2),
(5, 7, 4, 4, 7, 5),
(5, 7, 7, 5, 6, 6),
(1, 1, 0, 8, 4, 9),
(1, 1, 4, 9, 5, 7),
(2, 2, 1, 1, 5, 7),
(2, 2, 5, 7, 6, 6),
(3, 11, 2, 2, 6, 6),
(3, 11, 6, 6, 7, 10),
(7, 5, 4, 4, 0, 12),
(7, 5, 0, 12, 3, 13),
]
.into_iter()
.map(|(vx, tx, vy, ty, vz, tz)| Shape {
primitive: Triangle(
(vx, Some(tx), None),
(vy, Some(ty), None),
(vz, Some(tz), None),
),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
}],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn test_cube_tex_vert_missing_vw() {
use self::Primitive::Triangle;
let test_case = r#"
# Blender v2.71 (sub 0) OBJ File: 'cube.blend'
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.004952
vt 0.754996
vt 0.755393
vt 1.005349
vt 0.255083
vt 0.255480
vt 0.505437
vt 0.505039
vt 0.754598
vt 0.504642
vt 0.505834
vt 0.755790
vt 0.005127
vt 0.005524
usemtl Material
s off
f 1/1 2/2 3/3 4/4
f 5/5 8/6 7/7 6/8
f 1/9 5/10 6/8 2/2
f 2/2 6/8 7/7 3/3
f 3/3 7/7 8/11 4/12
f 5/5 1/13 4/14 8/6"#;
let expected = Ok(ObjSet {
material_library: Some("cube.mtl".to_owned()),
objects: vec![Object {
name: "Cube".to_owned(),
vertices: vec![
(1.0, -1.0, -1.0),
(1.0, -1.0, 1.0),
(-1.0, -1.0, 1.0),
(-1.0, -1.0, -1.0),
(1.0, 1.0, -1.0),
(1.0, 1.0, 1.0),
(-1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0),
]
.into_iter()
.map(|(x, y, z)| Vertex {
x: x as f64,
y: y as f64,
z: z as f64,
})
.collect(),
tex_vertices: vec![
1.004952, 0.754996, 0.755393, 1.005349, 0.255083, 0.25548, 0.505437, 0.505039, 0.754598,
0.504642, 0.505834, 0.75579, 0.005127, 0.005524,
]
.into_iter()
.map(|u| TVertex { u, v: 0., w: 0. })
.collect(),
normals: vec![],
geometry: vec![Geometry {
material_name: Some("Material".to_owned()),
shapes: vec![
(3, 3, 0, 0, 1, 1),
(3, 3, 1, 1, 2, 2),
(5, 7, 4, 4, 7, 5),
(5, 7, 7, 5, 6, 6),
(1, 1, 0, 8, 4, 9),
(1, 1, 4, 9, 5, 7),
(2, 2, 1, 1, 5, 7),
(2, 2, 5, 7, 6, 6),
(3, 11, 2, 2, 6, 6),
(3, 11, 6, 6, 7, 10),
(7, 5, 4, 4, 0, 12),
(7, 5, 0, 12, 3, 13),
]
.into_iter()
.map(|(vx, tx, vy, ty, vz, tz)| Shape {
primitive: Triangle(
(vx, Some(tx), None),
(vy, Some(ty), None),
(vz, Some(tz), None),
),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
}],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn test_cube_3d_tex_vert() {
use self::Primitive::Triangle;
let test_case = r#"
# Blender v2.71 (sub 0) OBJ File: 'cube.blend'
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.004952 0.498633 1.0
vt 0.754996 0.498236 1.0
vt 0.755393 0.248279 1.0
vt 1.005349 0.248677 1.0
vt 0.255083 0.497442 1.0
vt 0.255480 0.247485 1.0
vt 0.505437 0.247882 1.0
vt 0.505039 0.497839 1.0
vt 0.754598 0.748193 1.0
vt 0.504642 0.747795 1.0
vt 0.505834 -0.002074 1.0
vt 0.755790 -0.001677 1.0
vt 0.005127 0.497044 1.0
vt 0.005524 0.247088 1.0
usemtl Material
s off
f 1/1 2/2 3/3 4/4
f 5/5 8/6 7/7 6/8
f 1/9 5/10 6/8 2/2
f 2/2 6/8 7/7 3/3
f 3/3 7/7 8/11 4/12
f 5/5 1/13 4/14 8/6
"#;
let expected = Ok(ObjSet {
material_library: Some("cube.mtl".to_owned()),
objects: vec![Object {
name: "Cube".to_owned(),
vertices: vec![
(1.0, -1.0, -1.0),
(1.0, -1.0, 1.0),
(-1.0, -1.0, 1.0),
(-1.0, -1.0, -1.0),
(1.0, 1.0, -1.0),
(1.0, 1.0, 1.0),
(-1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0),
]
.into_iter()
.map(|(x, y, z)| Vertex {
x: x as f64,
y: y as f64,
z: z as f64,
})
.collect(),
tex_vertices: vec![
(1.004952, 0.498633),
(0.754996, 0.498236),
(0.755393, 0.248279),
(1.005349, 0.248677),
(0.255083, 0.497442),
(0.25548, 0.247485),
(0.505437, 0.247882),
(0.505039, 0.497839),
(0.754598, 0.748193),
(0.504642, 0.747795),
(0.505834, -0.002074),
(0.75579, -0.001677),
(0.005127, 0.497044),
(0.005524, 0.247088),
]
.into_iter()
.map(|(u, v)| TVertex { u, v, w: 1. })
.collect(),
normals: vec![],
geometry: vec![Geometry {
material_name: Some("Material".to_owned()),
shapes: vec![
(3, 3, 0, 0, 1, 1),
(3, 3, 1, 1, 2, 2),
(5, 7, 4, 4, 7, 5),
(5, 7, 7, 5, 6, 6),
(1, 1, 0, 8, 4, 9),
(1, 1, 4, 9, 5, 7),
(2, 2, 1, 1, 5, 7),
(2, 2, 5, 7, 6, 6),
(3, 11, 2, 2, 6, 6),
(3, 11, 6, 6, 7, 10),
(7, 5, 4, 4, 0, 12),
(7, 5, 0, 12, 3, 13),
]
.into_iter()
.map(|(vx, tx, vy, ty, vz, tz)| Shape {
primitive: Triangle(
(vx, Some(tx), None),
(vy, Some(ty), None),
(vz, Some(tz), None),
),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
}],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn test_normals_no_tex() {
use self::Primitive::Triangle;
let test_case = r#"
# Blender v2.70 (sub 4) OBJ File: ''
# www.blender.org
mtllib normal-cone.mtl
o Cone
v 0.000000 -1.000000 -1.000000
v 0.000000 1.000000 0.000000
v 0.195090 -1.000000 -0.980785
v 0.382683 -1.000000 -0.923880
v 0.555570 -1.000000 -0.831470
v 0.707107 -1.000000 -0.707107
v 0.831470 -1.000000 -0.555570
v 0.923880 -1.000000 -0.382683
v 0.980785 -1.000000 -0.195090
v 1.000000 -1.000000 -0.000000
v 0.980785 -1.000000 0.195090
v 0.923880 -1.000000 0.382683
v 0.831470 -1.000000 0.555570
v 0.707107 -1.000000 0.707107
v 0.555570 -1.000000 0.831470
v 0.382683 -1.000000 0.923880
v 0.195090 -1.000000 0.980785
v -0.000000 -1.000000 1.000000
v -0.195091 -1.000000 0.980785
v -0.382684 -1.000000 0.923879
v -0.555571 -1.000000 0.831469
v -0.707107 -1.000000 0.707106
v -0.831470 -1.000000 0.555570
v -0.923880 -1.000000 0.382683
v -0.980785 -1.000000 0.195089
v -1.000000 -1.000000 -0.000001
v -0.980785 -1.000000 -0.195091
v -0.923879 -1.000000 -0.382684
v -0.831469 -1.000000 -0.555571
v -0.707106 -1.000000 -0.707108
v -0.555569 -1.000000 -0.831470
v -0.382682 -1.000000 -0.923880
v -0.195089 -1.000000 -0.980786
vn -0.259887 0.445488 -0.856737
vn 0.087754 0.445488 -0.890977
vn -0.422035 0.445488 -0.789574
vn -0.567964 0.445488 -0.692068
vn -0.692066 0.445488 -0.567966
vn -0.789573 0.445488 -0.422037
vn -0.856737 0.445488 -0.259889
vn -0.890977 0.445488 -0.087754
vn -0.890977 0.445488 0.087753
vn -0.856737 0.445488 0.259887
vn -0.789574 0.445488 0.422035
vn -0.692067 0.445488 0.567964
vn -0.567965 0.445488 0.692066
vn -0.422036 0.445488 0.789573
vn -0.259889 0.445488 0.856737
vn -0.087754 0.445488 0.890977
vn 0.087753 0.445488 0.890977
vn 0.259888 0.445488 0.856737
vn 0.422036 0.445488 0.789573
vn 0.567965 0.445488 0.692067
vn 0.692067 0.445488 0.567965
vn 0.789573 0.445488 0.422035
vn 0.856737 0.445488 0.259888
vn 0.890977 0.445488 0.087753
vn 0.890977 0.445488 -0.087754
vn 0.856737 0.445488 -0.259888
vn 0.789573 0.445488 -0.422036
vn 0.692067 0.445488 -0.567965
vn 0.567965 0.445488 -0.692067
vn 0.422036 0.445488 -0.789573
vn -0.087753 0.445488 -0.890977
vn 0.259888 0.445488 -0.856737
vn 0.000000 -1.000000 -0.000000
usemtl Material.002
s off
f 32//1 2//1 33//1
f 1//2 2//2 3//2
f 31//3 2//3 32//3
f 30//4 2//4 31//4
f 29//5 2//5 30//5
f 28//6 2//6 29//6
f 27//7 2//7 28//7
f 26//8 2//8 27//8
f 25//9 2//9 26//9
f 24//10 2//10 25//10
f 23//11 2//11 24//11
f 22//12 2//12 23//12
f 21//13 2//13 22//13
f 20//14 2//14 21//14
f 19//15 2//15 20//15
f 18//16 2//16 19//16
f 17//17 2//17 18//17
f 16//18 2//18 17//18
f 15//19 2//19 16//19
f 14//20 2//20 15//20
f 13//21 2//21 14//21
f 12//22 2//22 13//22
f 11//23 2//23 12//23
f 10//24 2//24 11//24
f 9//25 2//25 10//25
f 8//26 2//26 9//26
f 7//27 2//27 8//27
f 6//28 2//28 7//28
f 5//29 2//29 6//29
f 4//30 2//30 5//30
f 33//31 2//31 1//31
f 3//32 2//32 4//32
"#;
let expected = Ok(ObjSet {
material_library: Some("normal-cone.mtl".to_owned()),
objects: vec![Object {
name: "Cone".to_owned(),
vertices: vec![
(0.000000, -1.000000, -1.000000),
(0.000000, 1.000000, 0.000000),
(0.195090, -1.000000, -0.980785),
(0.382683, -1.000000, -0.923880),
(0.555570, -1.000000, -0.831470),
(0.707107, -1.000000, -0.707107),
(0.831470, -1.000000, -0.555570),
(0.923880, -1.000000, -0.382683),
(0.980785, -1.000000, -0.195090),
(1.000000, -1.000000, -0.000000),
(0.980785, -1.000000, 0.195090),
(0.923880, -1.000000, 0.382683),
(0.831470, -1.000000, 0.555570),
(0.707107, -1.000000, 0.707107),
(0.555570, -1.000000, 0.831470),
(0.382683, -1.000000, 0.923880),
(0.195090, -1.000000, 0.980785),
(-0.000000, -1.000000, 1.000000),
(-0.195091, -1.000000, 0.980785),
(-0.382684, -1.000000, 0.923879),
(-0.555571, -1.000000, 0.831469),
(-0.707107, -1.000000, 0.707106),
(-0.831470, -1.000000, 0.555570),
(-0.923880, -1.000000, 0.382683),
(-0.980785, -1.000000, 0.195089),
(-1.000000, -1.000000, -0.000001),
(-0.980785, -1.000000, -0.195091),
(-0.923879, -1.000000, -0.382684),
(-0.831469, -1.000000, -0.555571),
(-0.707106, -1.000000, -0.707108),
(-0.555569, -1.000000, -0.831470),
(-0.382682, -1.000000, -0.923880),
(-0.195089, -1.000000, -0.980786),
]
.into_iter()
.map(|(x, y, z)| Vertex { x, y, z })
.collect(),
tex_vertices: vec![],
normals: vec![
(-0.259887, 0.445488, -0.856737),
(0.087754, 0.445488, -0.890977),
(-0.422035, 0.445488, -0.789574),
(-0.567964, 0.445488, -0.692068),
(-0.692066, 0.445488, -0.567966),
(-0.789573, 0.445488, -0.422037),
(-0.856737, 0.445488, -0.259889),
(-0.890977, 0.445488, -0.087754),
(-0.890977, 0.445488, 0.087753),
(-0.856737, 0.445488, 0.259887),
(-0.789574, 0.445488, 0.422035),
(-0.692067, 0.445488, 0.567964),
(-0.567965, 0.445488, 0.692066),
(-0.422036, 0.445488, 0.789573),
(-0.259889, 0.445488, 0.856737),
(-0.087754, 0.445488, 0.890977),
(0.087753, 0.445488, 0.890977),
(0.259888, 0.445488, 0.856737),
(0.422036, 0.445488, 0.789573),
(0.567965, 0.445488, 0.692067),
(0.692067, 0.445488, 0.567965),
(0.789573, 0.445488, 0.422035),
(0.856737, 0.445488, 0.259888),
(0.890977, 0.445488, 0.087753),
(0.890977, 0.445488, -0.087754),
(0.856737, 0.445488, -0.259888),
(0.789573, 0.445488, -0.422036),
(0.692067, 0.445488, -0.567965),
(0.567965, 0.445488, -0.692067),
(0.422036, 0.445488, -0.789573),
(-0.087753, 0.445488, -0.890977),
(0.259888, 0.445488, -0.856737),
(0.000000, -1.000000, -0.000000),
]
.into_iter()
.map(|(x, y, z)| Normal { x, y, z })
.collect(),
geometry: vec![Geometry {
material_name: Some("Material.002".to_owned()),
shapes: vec![
(32, 0, 31, 0, 1, 0),
(2, 1, 0, 1, 1, 1),
(31, 2, 30, 2, 1, 2),
(30, 3, 29, 3, 1, 3),
(29, 4, 28, 4, 1, 4),
(28, 5, 27, 5, 1, 5),
(27, 6, 26, 6, 1, 6),
(26, 7, 25, 7, 1, 7),
(25, 8, 24, 8, 1, 8),
(24, 9, 23, 9, 1, 9),
(23, 10, 22, 10, 1, 10),
(22, 11, 21, 11, 1, 11),
(21, 12, 20, 12, 1, 12),
(20, 13, 19, 13, 1, 13),
(19, 14, 18, 14, 1, 14),
(18, 15, 17, 15, 1, 15),
(17, 16, 16, 16, 1, 16),
(16, 17, 15, 17, 1, 17),
(15, 18, 14, 18, 1, 18),
(14, 19, 13, 19, 1, 19),
(13, 20, 12, 20, 1, 20),
(12, 21, 11, 21, 1, 21),
(11, 22, 10, 22, 1, 22),
(10, 23, 9, 23, 1, 23),
(9, 24, 8, 24, 1, 24),
(8, 25, 7, 25, 1, 25),
(7, 26, 6, 26, 1, 26),
(6, 27, 5, 27, 1, 27),
(5, 28, 4, 28, 1, 28),
(4, 29, 3, 29, 1, 29),
(0, 30, 32, 30, 1, 30),
(3, 31, 2, 31, 1, 31),
]
.into_iter()
.map(|(vx, nx, vy, ny, vz, nz)| Shape {
primitive: Triangle(
(vx, None, Some(nx)),
(vy, None, Some(ny)),
(vz, None, Some(nz)),
),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
}],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn test_smoothing_groups() {
use self::Primitive::Triangle;
let test_case = r#"
# Blender v2.72 (sub 0) OBJ File: 'dome.blend'
# www.blender.org
mtllib dome.mtl
o Dome
v -0.382683 0.923880 0.000000
v -0.707107 0.707107 0.000000
v -0.923880 0.382683 0.000000
v -1.000000 -0.000000 0.000000
v -0.270598 0.923880 -0.270598
v -0.500000 0.707107 -0.500000
v -0.653282 0.382683 -0.653281
v -0.707107 -0.000000 -0.707107
v -0.000000 0.923880 -0.382683
v -0.000000 0.707107 -0.707107
v -0.000000 0.382683 -0.923879
v -0.000000 -0.000000 -1.000000
v -0.000000 1.000000 0.000000
v 0.270598 0.923880 -0.270598
v 0.500000 0.707107 -0.500000
v 0.653281 0.382683 -0.653281
v 0.707106 -0.000000 -0.707107
v 0.382683 0.923880 -0.000000
v 0.707106 0.707107 -0.000000
v 0.923879 0.382683 -0.000000
v 1.000000 -0.000000 -0.000000
v 0.270598 0.923880 0.270598
v 0.500000 0.707107 0.500000
v 0.653281 0.382683 0.653281
v 0.707106 -0.000000 0.707107
v -0.000000 0.923880 0.382683
v -0.000000 0.707107 0.707107
v -0.000000 0.382683 0.923879
v -0.000000 -0.000000 1.000000
v -0.270598 0.923880 0.270598
v -0.500000 0.707107 0.500000
v -0.653281 0.382683 0.653281
v -0.707107 -0.000000 0.707107
usemtl None
s 1
f 4 3 7
f 3 2 6
f 1 5 6
f 7 11 12
f 6 10 11
f 5 9 10
f 11 16 17
f 11 10 15
f 10 9 14
f 16 20 21
f 15 19 20
f 14 18 19
f 20 24 25
f 20 19 23
f 18 22 23
f 24 28 29
f 24 23 27
f 23 22 26
f 28 32 33
f 28 27 31
f 27 26 30
f 1 13 5
f 5 13 9
f 9 13 14
f 14 13 18
f 18 13 22
f 22 13 26
f 26 13 30
f 32 3 4
f 31 2 3
f 30 1 2
f 30 13 1
f 8 4 7
f 7 3 6
f 2 1 6
f 8 7 12
f 7 6 11
f 6 5 10
f 12 11 17
f 16 11 15
f 15 10 14
f 17 16 21
f 16 15 20
f 15 14 19
f 21 20 25
f 24 20 23
f 19 18 23
f 25 24 29
f 28 24 27
f 27 23 26
f 29 28 33
f 32 28 31
f 31 27 30
f 33 32 4
f 32 31 3
f 31 30 2
s 2
f 33 4 8
f 29 33 25
f 12 17 21
f 12 33 8
f 33 21 25
f 21 33 12
"#;
let expected = Ok(ObjSet {
material_library: Some("dome.mtl".to_owned()),
objects: vec![Object {
name: "Dome".to_owned(),
vertices: vec![
(-0.382683, 0.92388, 0.0),
(-0.707107, 0.707107, 0.0),
(-0.92388, 0.382683, 0.0),
(-1.0, 0.0, 0.0),
(-0.270598, 0.92388, -0.270598),
(-0.5, 0.707107, -0.5),
(-0.653282, 0.382683, -0.653281),
(-0.707107, 0.0, -0.707107),
(0.0, 0.92388, -0.382683),
(0.0, 0.707107, -0.707107),
(0.0, 0.382683, -0.923879),
(0.0, 0.0, -1.0),
(0.0, 1.0, 0.0),
(0.270598, 0.92388, -0.270598),
(0.5, 0.707107, -0.5),
(0.653281, 0.382683, -0.653281),
(0.707106, 0.0, -0.707107),
(0.382683, 0.92388, 0.0),
(0.707106, 0.707107, 0.0),
(0.923879, 0.382683, 0.0),
(1.0, 0.0, 0.0),
(0.270598, 0.92388, 0.270598),
(0.5, 0.707107, 0.5),
(0.653281, 0.382683, 0.653281),
(0.707106, 0.0, 0.707107),
(0.0, 0.92388, 0.382683),
(0.0, 0.707107, 0.707107),
(0.0, 0.382683, 0.923879),
(0.0, 0.0, 1.0),
(-0.270598, 0.92388, 0.270598),
(-0.5, 0.707107, 0.5),
(-0.653281, 0.382683, 0.653281),
(-0.707107, 0.0, 0.707107),
]
.into_iter()
.map(|(x, y, z)| Vertex { x, y, z })
.collect(),
tex_vertices: vec![],
normals: vec![],
geometry: vec![Geometry {
material_name: Some("None".to_owned()),
shapes: vec![
(6, 3, 2, 1),
(5, 2, 1, 1),
(5, 0, 4, 1),
(11, 6, 10, 1),
(10, 5, 9, 1),
(9, 4, 8, 1),
(16, 10, 15, 1),
(14, 10, 9, 1),
(13, 9, 8, 1),
(20, 15, 19, 1),
(19, 14, 18, 1),
(18, 13, 17, 1),
(24, 19, 23, 1),
(22, 19, 18, 1),
(22, 17, 21, 1),
(28, 23, 27, 1),
(26, 23, 22, 1),
(25, 22, 21, 1),
(32, 27, 31, 1),
(30, 27, 26, 1),
(29, 26, 25, 1),
(4, 0, 12, 1),
(8, 4, 12, 1),
(13, 8, 12, 1),
(17, 13, 12, 1),
(21, 17, 12, 1),
(25, 21, 12, 1),
(29, 25, 12, 1),
(3, 31, 2, 1),
(2, 30, 1, 1),
(1, 29, 0, 1),
(0, 29, 12, 1),
(6, 7, 3, 1),
(5, 6, 2, 1),
(5, 1, 0, 1),
(11, 7, 6, 1),
(10, 6, 5, 1),
(9, 5, 4, 1),
(16, 11, 10, 1),
(14, 15, 10, 1),
(13, 14, 9, 1),
(20, 16, 15, 1),
(19, 15, 14, 1),
(18, 14, 13, 1),
(24, 20, 19, 1),
(22, 23, 19, 1),
(22, 18, 17, 1),
(28, 24, 23, 1),
(26, 27, 23, 1),
(25, 26, 22, 1),
(32, 28, 27, 1),
(30, 31, 27, 1),
(29, 30, 26, 1),
(3, 32, 31, 1),
(2, 31, 30, 1),
(1, 30, 29, 1),
(7, 32, 3, 2),
(24, 28, 32, 2),
(20, 11, 16, 2),
(7, 11, 32, 2),
(24, 32, 20, 2),
(11, 20, 32, 2),
]
.into_iter()
.map(|(x, y, z, s)| Shape {
primitive: Triangle((x, None, None), (y, None, None), (z, None, None)),
groups: vec![],
smoothing_groups: vec![s],
})
.collect(),
}],
}],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn no_mtls() {
use self::Primitive::Triangle;
let test_case = r#"
# Blender v2.71 (sub 0) OBJ File: 'cube.blend'
# www.blender.org
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.004952 0.498633
vt 0.754996 0.498236
vt 0.755393 0.248279
vt 1.005349 0.248677
vt 0.255083 0.497442
vt 0.255480 0.247485
vt 0.505437 0.247882
vt 0.505039 0.497839
vt 0.754598 0.748193
vt 0.504642 0.747795
vt 0.505834 -0.002074
vt 0.755790 -0.001677
vt 0.005127 0.497044
vt 0.005524 0.247088
s off
f 1/1 2/2 3/3 4/4
f 5/5 8/6 7/7 6/8
f 1/9 5/10 6/8 2/2
f 2/2 6/8 7/7 3/3
f 3/3 7/7 8/11 4/12
f 5/5 1/13 4/14 8/6
"#;
let expected = Ok(ObjSet {
material_library: None,
objects: vec![Object {
name: "Cube".to_owned(),
vertices: vec![
(1.0, -1.0, -1.0),
(1.0, -1.0, 1.0),
(-1.0, -1.0, 1.0),
(-1.0, -1.0, -1.0),
(1.0, 1.0, -1.0),
(1.0, 1.0, 1.0),
(-1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0),
]
.into_iter()
.map(|(x, y, z)| Vertex { x, y, z })
.collect(),
tex_vertices: vec![
(1.004952, 0.498633),
(0.754996, 0.498236),
(0.755393, 0.248279),
(1.005349, 0.248677),
(0.255083, 0.497442),
(0.25548, 0.247485),
(0.505437, 0.247882),
(0.505039, 0.497839),
(0.754598, 0.748193),
(0.504642, 0.747795),
(0.505834, -0.002074),
(0.75579, -0.001677),
(0.005127, 0.497044),
(0.005524, 0.247088),
]
.into_iter()
.map(|(u, v)| TVertex { u, v, w: 0. })
.collect(),
normals: vec![],
geometry: vec![Geometry {
material_name: None,
shapes: vec![
(3, 3, 0, 0, 1, 1),
(3, 3, 1, 1, 2, 2),
(5, 7, 4, 4, 7, 5),
(5, 7, 7, 5, 6, 6),
(1, 1, 0, 8, 4, 9),
(1, 1, 4, 9, 5, 7),
(2, 2, 1, 1, 5, 7),
(2, 2, 5, 7, 6, 6),
(3, 11, 2, 2, 6, 6),
(3, 11, 6, 6, 7, 10),
(7, 5, 4, 4, 0, 12),
(7, 5, 0, 12, 3, 13),
]
.into_iter()
.map(|(vx, tx, vy, ty, vz, tz)| Shape {
primitive: Triangle(
(vx, Some(tx), None),
(vy, Some(ty), None),
(vz, Some(tz), None),
),
groups: vec![],
smoothing_groups: vec![],
})
.collect(),
}],
}],
});
assert_eq!(parse(test_case), expected);
}
#[test]
fn one_group() {
use self::Primitive::Triangle;
let input = "
o Cube
v 1.000000 -1.000000 -1.000000\r
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.004952 0.498633
vt 0.754996 0.498236
vt 0.755393 0.248279
vt 1.005349 0.248677
vt 0.255083 0.497442
vt 0.255480 0.247485
vt 0.505437 0.247882
vt 0.505039 0.497839
vt 0.754598 0.748193
vt 0.504642 0.747795
vt 0.505834 -0.002074
vt 0.755790 -0.001677
vt 0.005127 0.497044
vt 0.005524 0.247088
g all
s off
f 1/1 2/2 3/3 4/4
f 5/5 8/6 7/7 6/8
f 1/9 5/10 6/8 2/2
f 2/2 6/8 7/7 3/3
f 3/3 7/7 8/11 4/12
f 5/5 1/13 4/14 8/6
";
let expected = ObjSet {
material_library: None,
objects: vec![Object {
name: "Cube".to_owned(),
vertices: vec![
(1.0, -1.0, -1.0),
(1.0, -1.0, 1.0),
(-1.0, -1.0, 1.0),
(-1.0, -1.0, -1.0),
(1.0, 1.0, -1.0),
(1.0, 1.0, 1.0),
(-1.0, 1.0, 1.0),
(-1.0, 1.0, -1.0),
]
.into_iter()
.map(|(x, y, z)| Vertex { x, y, z })
.collect(),
tex_vertices: vec![
(1.004952, 0.498633),
(0.754996, 0.498236),
(0.755393, 0.248279),
(1.005349, 0.248677),
(0.255083, 0.497442),
(0.25548, 0.247485),
(0.505437, 0.247882),
(0.505039, 0.497839),
(0.754598, 0.748193),
(0.504642, 0.747795),
(0.505834, -0.002074),
(0.75579, -0.001677),
(0.005127, 0.497044),
(0.005524, 0.247088),
]
.into_iter()
.map(|(u, v)| TVertex { u, v, w: 0. })
.collect(),
normals: vec![],
geometry: vec![Geometry {
material_name: None,
shapes: vec![
(3, 3, 0, 0, 1, 1, "all"),
(3, 3, 1, 1, 2, 2, "all"),
(5, 7, 4, 4, 7, 5, "all"),
(5, 7, 7, 5, 6, 6, "all"),
(1, 1, 0, 8, 4, 9, "all"),
(1, 1, 4, 9, 5, 7, "all"),
(2, 2, 1, 1, 5, 7, "all"),
(2, 2, 5, 7, 6, 6, "all"),
(3, 11, 2, 2, 6, 6, "all"),
(3, 11, 6, 6, 7, 10, "all"),
(7, 5, 4, 4, 0, 12, "all"),
(7, 5, 0, 12, 3, 13, "all"),
]
.into_iter()
.map(|(xv, xt, yv, yt, zv, zt, group)| Shape {
primitive: Triangle(
(xv, Some(xt), None),
(yv, Some(yt), None),
(zv, Some(zt), None),
),
groups: vec![group.into()],
smoothing_groups: vec![],
})
.collect(),
}],
}],
};
assert_eq!(parse(input), Ok(expected));
}
#[test]
fn issue_54() {
let input = include_str!("issue_54.obj");
let _ = parse(input);
}
#[test]
fn several_groups() {
use self::Primitive::Triangle;
let input = r#"
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 1.004952 0.498633
vt 0.754996 0.498236
vt 0.755393 0.248279
vt 1.005349 0.248677
vt 0.255083 0.497442
vt 0.255480 0.247485
vt 0.505437 0.247882
vt 0.505039 0.497839
vt 0.754598 0.748193
vt 0.504642 0.747795
vt 0.505834 -0.002074
vt 0.755790 -0.001677
vt 0.005127 0.497044
vt 0.005524 0.247088
s off
g face one
f 1/1 2/2 3/3 4/4
g face two
f 5/5 8/6 7/7 6/8
g face three
f 1/9 5/10 6/8 2/2
g face four
f 2/2 6/8 7/7 3/3
g face five
f 3/3 7/7 8/11 4/12
g face six
f 5/5 1/13 4/14 8/6"#;
let expected = ObjSet {
material_library: None,
objects: vec![Object {
name: "Cube".to_owned(),
vertices: vec![
Vertex {
x: 1.0,
y: -1.0,
z: -1.0,
},
Vertex {
x: 1.0,
y: -1.0,
z: 1.0,
},
Vertex {
x: -1.0,
y: -1.0,
z: 1.0,
},
Vertex {
x: -1.0,
y: -1.0,
z: -1.0,
},
Vertex {
x: 1.0,
y: 1.0,
z: -1.0,
},
Vertex {
x: 1.0,
y: 1.0,
z: 1.0,
},
Vertex {
x: -1.0,
y: 1.0,
z: 1.0,
},
Vertex {
x: -1.0,
y: 1.0,
z: -1.0,
},
],
tex_vertices: vec![
TVertex {
u: 1.004952,
v: 0.498633,
w: 0.,
},
TVertex {
u: 0.754996,
v: 0.498236,
w: 0.,
},
TVertex {
u: 0.755393,
v: 0.248279,
w: 0.,
},
TVertex {
u: 1.005349,
v: 0.248677,
w: 0.,
},
TVertex {
u: 0.255083,
v: 0.497442,
w: 0.,
},
TVertex {
u: 0.25548,
v: 0.247485,
w: 0.,
},
TVertex {
u: 0.505437,
v: 0.247882,
w: 0.,
},
TVertex {
u: 0.505039,
v: 0.497839,
w: 0.,
},
TVertex {
u: 0.754598,
v: 0.748193,
w: 0.,
},
TVertex {
u: 0.504642,
v: 0.747795,
w: 0.,
},
TVertex {
u: 0.505834,
v: -0.002074,
w: 0.,
},
TVertex {
u: 0.75579,
v: -0.001677,
w: 0.,
},
TVertex {
u: 0.005127,
v: 0.497044,
w: 0.,
},
TVertex {
u: 0.005524,
v: 0.247088,
w: 0.,
},
],
normals: vec![],
geometry: vec![Geometry {
material_name: None,
shapes: vec![
(3, 3, 0, 0, 1, 1, vec!["face", "one"]),
(3, 3, 1, 1, 2, 2, vec!["face", "one"]),
(5, 7, 4, 4, 7, 5, vec!["face", "two"]),
(5, 7, 7, 5, 6, 6, vec!["face", "two"]),
(1, 1, 0, 8, 4, 9, vec!["face", "three"]),
(1, 1, 4, 9, 5, 7, vec!["face", "three"]),
(2, 2, 1, 1, 5, 7, vec!["face", "four"]),
(2, 2, 5, 7, 6, 6, vec!["face", "four"]),
(3, 11, 2, 2, 6, 6, vec!["face", "five"]),
(3, 11, 6, 6, 7, 10, vec!["face", "five"]),
(7, 5, 4, 4, 0, 12, vec!["face", "six"]),
(7, 5, 0, 12, 3, 13, vec!["face", "six"]),
]
.into_iter()
.map(|(vx, tx, vy, ty, vz, tz, groups)| Shape {
primitive: Triangle(
(vx, Some(tx), None),
(vy, Some(ty), None),
(vz, Some(tz), None),
),
groups: groups.into_iter().map(|s| s.into()).collect(),
smoothing_groups: vec![],
})
.collect(),
}],
}],
};
assert_eq!(parse(input), Ok(expected));
}
pub fn parse<S: AsRef<str>>(input: S) -> Result<ObjSet, ParseError> {
Parser::new(input.as_ref()).parse_objset()
}