use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::hash::Hash;
use std::io::BufRead;
use std::mem;
use crate::error::{make_error, ObjResult};
use crate::raw::lexer::lex;
use crate::raw::util::parse_args;
macro_rules! parse_args {
{
$first:expr, $rest:expr,
$($pat:pat => $type:ident::$name:ident[$exp:expr]),*,
! => $error:expr
} => (
match split_vertex_group($first)[..] {
$($pat => $type::$name({
let mut points = vec![$exp];
for param in $rest {
match split_vertex_group(param)[..] {
$pat => points.push($exp),
_ => $error
}
}
points
}),)*
_ => $error
}
)
}
fn try_index<T>(collection: &[T], input: &str) -> ObjResult<usize> {
use crate::error::{LoadError, LoadErrorKind, ObjError};
let len: isize = collection.len().try_into().map_err(|_| {
ObjError::Load(LoadError::new_internal(
LoadErrorKind::IndexOutOfRange,
"Too many items in collection".to_string(),
))
})?;
let index: isize = input.parse()?;
let ret = if index < -len {
make_error!(IndexOutOfRange, "Too small index value");
} else if index < 0 {
len + index
} else if index == 0 {
make_error!(IndexOutOfRange, "Index value shouldn't be zero");
} else if index <= len {
index - 1
} else {
make_error!(IndexOutOfRange, "Too big index value");
};
Ok(ret as usize)
}
pub fn parse_obj<T: BufRead>(input: T) -> ObjResult<RawObj> {
let mut name = None;
let mut material_libraries = Vec::new();
let mut positions = Vec::new();
let mut tex_coords = Vec::new();
let mut normals = Vec::new();
let mut param_vertices = Vec::new();
let mut points = Vec::new();
let mut lines = Vec::new();
let mut polygons = Vec::new();
let counter = Counter::new(&points, &lines, &polygons);
let mut group_builder = GroupBuilder::with_default(&counter, String::from("default"));
let mut mesh_builder = GroupBuilder::with_default(&counter, String::new());
let mut smoothing_builder = GroupBuilder::new(&counter);
let mut merging_builder = GroupBuilder::new(&counter);
lex(input, |stmt, args: &[&str]| {
match stmt {
"v" => positions.push(match parse_args(args)?[..] {
[x, y, z, w] => (x, y, z, w),
[x, y, z] => (x, y, z, 1.0),
_ => make_error!(WrongNumberOfArguments, "Expected 3 or 4 arguments"),
}),
"vt" => tex_coords.push(match parse_args(args)?[..] {
[u, v, w] => (u, v, w),
[u, v] => (u, v, 0.0),
[u] => (u, 0.0, 0.0),
_ => make_error!(WrongNumberOfArguments, "Expected 1, 2 or 3 arguments"),
}),
"vn" => normals.push(match parse_args(args)?[..] {
[x, y, z] => (x, y, z),
_ => make_error!(WrongNumberOfArguments, "Expected 3 arguments"),
}),
"vp" => param_vertices.push(match parse_args(args)?[..] {
[u, v, w] => (u, v, w),
[u, v] => (u, v, 1.0),
[u] => (u, 0.0, 1.0),
_ => make_error!(WrongNumberOfArguments, "Expected 1, 2 or 3 arguments"),
}),
"cstype" => {
let geometry = match args {
["rat", ty] => *ty,
[ty] => *ty,
_ => make_error!(WrongTypeOfArguments, "Expected 'rat xxx' or 'xxx' format"),
};
match geometry {
"bmatrix" => unimplemented!(),
"bezier" => unimplemented!(),
"bspline" => unimplemented!(),
"cardinal" => unimplemented!(),
"taylor" => unimplemented!(),
_ => make_error!(
WrongTypeOfArguments,
"Expected one of 'bmatrix', 'bezier', 'bspline', 'cardinal' and 'taylor'"
),
}
}
"deg" => match parse_args(args)?[..] {
[_deg_u, _deg_v] => unimplemented!(),
[_deg_u] => unimplemented!(),
_ => make_error!(WrongNumberOfArguments, "Expected 1 or 2 arguments"),
},
"bmat" => unimplemented!(),
"step" => unimplemented!(),
"p" => {
for v in args {
let v = try_index(&positions, v)?;
points.push(v);
}
}
"l" => match args {
[] => make_error!(WrongNumberOfArguments, "Expected at least 2 arguments"),
[first, rest @ ..] => {
if args.len() < 2 {
make_error!(WrongNumberOfArguments, "Expected at least 2 arguments")
}
let line = parse_args! {
first, rest,
[p] => Line::P[try_index(&positions, p)?],
[p, t] => Line::PT[(try_index(&positions, p)?, try_index(&tex_coords, t)?)],
! => make_error!(WrongTypeOfArguments, "Unexpected vertex format, expected `#`, or `#/#`")
};
lines.push(line);
}
},
"fo" | "f" => match args {
[] => make_error!(WrongNumberOfArguments, "Expected at least 3 arguments"),
[first, rest @ ..] => {
if args.len() < 3 {
make_error!(WrongNumberOfArguments, "Expected at least 3 arguments")
}
let polygon = parse_args! {
first, rest,
[p] => Polygon::P[try_index(&positions, p)?],
[p, t] => Polygon::PT[(try_index(&positions, p)?, try_index(&tex_coords, t)?)],
[p, "", n] => Polygon::PN[(try_index(&positions, p)?, try_index(&normals, n)?)],
[p, t, n] => Polygon::PTN[(try_index(&positions, p)?, try_index(&tex_coords, t)?, try_index(&normals, n)?)],
! => make_error!(WrongTypeOfArguments, "Unexpected vertex format, expected `#`, `#/#`, `#//#`, or `#/#/#`")
};
polygons.push(polygon);
}
},
"curv" => unimplemented!(),
"curv2" => unimplemented!(),
"surf" => unimplemented!(),
"parm" => unimplemented!(),
"trim" => unimplemented!(),
"hole" => unimplemented!(),
"scrv" => unimplemented!(),
"sp" => unimplemented!(),
"end" => unimplemented!(),
"con" => unimplemented!(),
"g" => match args {
[name] => group_builder.start((*name).to_string()),
_ => make_error!(
WrongNumberOfArguments,
"Expected group name parameter, but nothing has been supplied"
),
},
"s" => match args {
["off"] | ["0"] => smoothing_builder.end(),
[param] => smoothing_builder.start(param.parse()?),
_ => make_error!(WrongNumberOfArguments, "Expected only 1 argument"),
},
"mg" => match args {
["off"] | ["0"] => merging_builder.end(),
[param] => merging_builder.start(param.parse()?),
_ => make_error!(WrongNumberOfArguments, "Expected only 1 argument"),
},
"o" => {
name = match args {
[] => None,
_ => Some(args.join(" ")),
}
}
"bevel" => unimplemented!(),
"c_interp" => unimplemented!(),
"d_interp" => unimplemented!(),
"lod" => unimplemented!(),
"usemtl" => match args {
[material] => mesh_builder.start((*material).to_string()),
_ => make_error!(WrongNumberOfArguments, "Expected only 1 argument"),
},
"mtllib" => {
material_libraries.reserve(args.len());
for &path in args {
material_libraries.push(path.to_string());
}
}
"shadow_obj" => unimplemented!(),
"trace_obj" => unimplemented!(),
"ctech" => unimplemented!(),
"stech" => unimplemented!(),
_ => make_error!(UnexpectedStatement, "Received unknown statement"),
}
Ok(())
})?;
group_builder.end();
mesh_builder.end();
smoothing_builder.end();
merging_builder.end();
Ok(RawObj {
name,
material_libraries,
positions,
tex_coords,
normals,
param_vertices,
points,
lines,
polygons,
groups: group_builder.result,
meshes: mesh_builder.result,
smoothing_groups: smoothing_builder.result,
merging_groups: merging_builder.result,
})
}
fn split_vertex_group(input: &str) -> Vec<&str> {
input.split('/').collect()
}
struct Counter {
points: *const Vec<Point>,
lines: *const Vec<Line>,
polygons: *const Vec<Polygon>,
}
impl Counter {
fn new(
points: *const Vec<Point>,
lines: *const Vec<Line>,
polygons: *const Vec<Polygon>,
) -> Self {
Counter {
points,
lines,
polygons,
}
}
fn get(&self) -> (usize, usize, usize) {
unsafe {
(
(*self.points).len(),
(*self.lines).len(),
(*self.polygons).len(),
)
}
}
}
struct GroupBuilder<'a, K> {
counter: &'a Counter,
current: Option<K>,
result: HashMap<K, Group>,
}
impl<'a, K> GroupBuilder<'a, K>
where
K: Clone + Eq + Hash,
{
fn new(counter: &'a Counter) -> Self {
GroupBuilder {
counter,
current: None,
result: HashMap::new(),
}
}
fn with_default(counter: &'a Counter, default: K) -> Self {
let mut result = HashMap::with_capacity(1);
result.insert(default.clone(), Group::new((0, 0, 0)));
GroupBuilder {
counter,
current: Some(default),
result,
}
}
fn start(&mut self, input: K) {
let count = self.counter.get();
match self.current {
Some(ref current) if *current == input => return,
Some(ref mut current) => {
let past = mem::replace(current, input.clone());
match self.result.entry(past) {
Entry::Vacant(_) => unreachable!(),
Entry::Occupied(mut e) => {
let was_empty_group = e.get_mut().end(count);
if was_empty_group {
e.remove();
}
}
}
}
None => self.current = Some(input.clone()),
}
self.result
.entry(input)
.and_modify(|e| e.start(count))
.or_insert_with(|| Group::new(count));
}
fn end(&mut self) {
match self.current.take() {
None => {}
Some(current) => {
match self.result.entry(current) {
Entry::Vacant(_) => unreachable!(),
Entry::Occupied(mut e) => {
let count = self.counter.get();
let was_empty_group = e.get_mut().end(count);
if was_empty_group {
e.remove();
}
}
}
}
}
}
}
const UNDEFINED: usize = usize::MAX;
impl Group {
fn new(count: (usize, usize, usize)) -> Self {
let mut ret = Group {
points: Vec::with_capacity(1),
lines: Vec::with_capacity(1),
polygons: Vec::with_capacity(1),
};
ret.start(count);
ret
}
fn start(&mut self, count: (usize, usize, usize)) {
self.points.push(Range {
start: count.0,
end: UNDEFINED,
});
self.lines.push(Range {
start: count.1,
end: UNDEFINED,
});
self.polygons.push(Range {
start: count.2,
end: UNDEFINED,
})
}
fn end(&mut self, count: (usize, usize, usize)) -> bool {
end(&mut self.points, count.0);
end(&mut self.lines, count.1);
end(&mut self.polygons, count.2);
fn end(vec: &mut Vec<Range>, end: usize) {
let last = vec.len() - 1;
assert_eq!(vec[last].end, UNDEFINED);
if vec[last].start != end {
vec[last].end = end;
} else {
vec.pop();
}
}
self.points.is_empty() && self.lines.is_empty() && self.polygons.is_empty()
}
}
#[derive(Clone, PartialEq, Debug, Default)]
pub struct RawObj {
pub name: Option<String>,
pub material_libraries: Vec<String>,
pub positions: Vec<(f32, f32, f32, f32)>,
pub tex_coords: Vec<(f32, f32, f32)>,
pub normals: Vec<(f32, f32, f32)>,
pub param_vertices: Vec<(f32, f32, f32)>,
pub points: Vec<Point>,
pub lines: Vec<Line>,
pub polygons: Vec<Polygon>,
pub groups: HashMap<String, Group>,
pub meshes: HashMap<String, Group>,
pub smoothing_groups: HashMap<usize, Group>,
pub merging_groups: HashMap<usize, Group>,
}
pub type Point = usize;
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum Line {
P(Vec<usize>),
PT(Vec<(usize, usize)>),
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum Polygon {
P(Vec<usize>),
PT(Vec<(usize, usize)>),
PN(Vec<(usize, usize)>),
PTN(Vec<(usize, usize, usize)>),
}
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct Group {
pub points: Vec<Range>,
pub lines: Vec<Range>,
pub polygons: Vec<Range>,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct Range {
pub start: usize,
pub end: usize,
}