mod gl {
pub use gl::types::*;
pub use gl::*;
}
#[derive(Debug,PartialEq)]
pub struct OBJError(String);
#[derive(Debug,PartialEq)]
pub struct Vertex {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
#[derive(Debug,PartialEq)]
pub struct UV {
pub u: f32,
pub v: f32,
}
#[derive(Debug,PartialEq)]
pub struct Normal {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Debug,PartialEq)]
pub struct Point {
vertex: usize,
uv: Option<usize>,
normal: Option<usize>,
}
impl Point {
fn parse(s: &str) -> Result<Point, OBJError> {
let err = || OBJError(format!("Invalid point: {}", s));
let mut chunks = s.split("/");
let vertex = chunks.next().ok_or(err())?.parse().map_err(|_| err())?;
let uv = match chunks.next() {
Some("") => None,
Some(n) => Some(n.parse().map_err(|_| err())?),
None => None,
};
let normal = match chunks.next() {
Some("") => None,
Some(n) => Some(n.parse().map_err(|_| err())?),
None => None,
};
Ok(Point { vertex, uv, normal, })
}
}
#[derive(Debug,PartialEq)]
pub struct Face(Vec<Point>);
#[derive(Debug,PartialEq)]
pub struct OBJFile {
pub vertices: Vec<Vertex>,
pub texcoords: Vec<UV>,
pub normals: Vec<Normal>,
pub faces: Vec<Face>,
}
impl OBJFile {
pub fn parse(s: &str) -> Result<OBJFile, OBJError> {
let mut vertices = Vec::new();
let mut texcoords = Vec::new();
let mut normals = Vec::new();
let mut faces = Vec::new();
for line in s.lines() {
let err = || OBJError(format!("Invalid line: {}", line));
let mut chunks = line.split_whitespace();
match chunks.next() {
Some("v") => vertices.push(Vertex {
x: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
y: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
z: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
w: 1.0,
}),
Some("vt") => texcoords.push(UV {
u: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
v: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
}),
Some("vn") => normals.push(Normal {
x: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
y: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
z: chunks.next().ok_or(err())?.parse().map_err(|_| err())?,
}),
Some("f") =>
match chunks.map(Point::parse).collect() {
Ok(ps) => faces.push(Face(ps)),
Err(e) => return Err(e),
}
_ => (),
}
}
Ok(OBJFile { vertices, texcoords, normals, faces })
}
pub fn to_bare_object(&self) -> Result<Vec<gl::GLfloat>, OBJError> {
let mut rs = Vec::new();
for &Face(ref f) in self.faces.iter() {
if f.len() != 3 {
return Err(OBJError(format!("Face length not, uh, three")));
}
for pt in f {
let vertex: &Vertex = self
.vertices.get(pt.vertex - 1)
.ok_or(OBJError(format!("Face length not, uh, three")))?;
rs.push(vertex.x);
rs.push(vertex.y);
rs.push(vertex.z);
}
}
Ok(rs)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn just_a_vertex() {
assert_eq!(
OBJFile::parse("v 1.0 2.0 3.0\n").unwrap(),
OBJFile {
vertices: vec![ Vertex { x: 1.0, y: 2.0, z: 3.0, w: 1.0 } ],
texcoords: vec![],
normals: vec![],
faces: vec![],
},
);
}
#[test]
fn just_a_uv() {
assert_eq!(
OBJFile::parse("vt 1.0 0.5\n").unwrap(),
OBJFile {
vertices: vec![],
texcoords: vec![ UV { u: 1.0, v: 0.5 } ],
normals: vec![],
faces: vec![],
},
);
}
#[test]
fn simple_tri() {
assert_eq!(
OBJFile::parse("v 0 0 0
v 0 1 0
v 1 0 0
f 1 2 3").unwrap(),
OBJFile {
vertices: vec![
Vertex { x: 0.0, y: 0.0, z: 0.0, w: 1.0 },
Vertex { x: 0.0, y: 1.0, z: 0.0, w: 1.0 },
Vertex { x: 1.0, y: 0.0, z: 0.0, w: 1.0 },
],
texcoords: vec![],
normals: vec![],
faces: vec![ Face(vec![
Point { vertex: 1, uv: None, normal: None },
Point { vertex: 2, uv: None, normal: None },
Point { vertex: 3, uv: None, normal: None },
]) ],
},
);
}
#[test]
fn simple_tri_with_uvs() {
assert_eq!(
OBJFile::parse("v 0 0 0
v 0 1 0
v 1 0 0
vt 0 0
vt 1 1
vt 1 0
f 1/1 2/2 3/3\n").unwrap(),
OBJFile {
vertices: vec![
Vertex { x: 0.0, y: 0.0, z: 0.0, w: 1.0 },
Vertex { x: 0.0, y: 1.0, z: 0.0, w: 1.0 },
Vertex { x: 1.0, y: 0.0, z: 0.0, w: 1.0 },
],
texcoords: vec![
UV { u: 0.0, v: 0.0 },
UV { u: 1.0, v: 1.0 },
UV { u: 1.0, v: 0.0 },
],
normals: vec![],
faces: vec![ Face(vec![
Point { vertex: 1, uv: Some(1), normal: None },
Point { vertex: 2, uv: Some(2), normal: None },
Point { vertex: 3, uv: Some(3), normal: None },
]) ],
},
);
}
}