use std::io;
use std::str::Split;
use std::collections::{HashMap, BTreeMap};
use std::io::{BufReader, BufRead};
use std::path::Path;
use std::fs::File;
use crate::draw::{ObjDef, Vertex, load_data_to_gpu, MtlInfo, Light};
use glium::{Display, texture::Texture2d};
use derive_more::{Error, From};
use crate::quadoctree::{QuadOctreeNode, QuadOctreeError, add_obj_to_quadoctree};
use crate::textures::{load_texture, TextureLoadError};
use crate::assets::find_asset;
const COLLISION_PREFIX: &str = "collision_";
const LIGHT_PREFIX: &str = "light_";
const TERRAIN_PREFIX: &str = "terrain_";
const MISC_PREFIX: &str = "misc_";
#[derive(Debug, derive_more::Display, Error, From)]
pub enum WavefrontLoadError {
#[from(ignore)]
FormatError { msg: &'static str },
#[from(ignore)]
BadIndexError { msg: &'static str },
IOError(io::Error),
FloatParseError(std::num::ParseFloatError),
IntParseError(std::num::ParseIntError),
TextureLoadError(TextureLoadError),
QuadOctreeCreateError(QuadOctreeError)
}
#[derive(PartialEq)]
enum MeshType {
Normal,
Terrain,
Collision,
Light,
Misc
}
fn parse_vertex_or_normal(split: &mut Split<char>, scale: &[f32; 3]) -> Result<[f32; 3], WavefrontLoadError> {
let mut components = [0.0, 0.0, 0.0f32];
for i in 0..3 {
let str_val = split.next().ok_or(WavefrontLoadError::FormatError { msg: "Vertex or normal has too few components" })?;
components[i] = str_val.trim().parse()?;
components[i] *= scale[i];
}
Ok(components)
}
fn parse_texcoords(split: &mut Split<char>) -> Result<[f32; 2], WavefrontLoadError> {
let mut components = [0.0, 0.0f32];
for i in 0..2 {
let str_val = split.next().ok_or(WavefrontLoadError::FormatError { msg: "Tex coords have too few components" })?;
components[i] = str_val.trim().parse()?;
}
Ok(components)
}
fn parse_face(split: &mut Split<char>, vertex_info: &Vec<[f32; 3]>, normal_info: &Vec<[f32; 3]>,
texcoord_info: &Vec<[f32; 2]>, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>) -> Result<(), WavefrontLoadError> {
let mut face_indices: Vec<u32> = Vec::new();
for i in 0..4 {
let face_index_txt = split.next();
let face_index_txt = match face_index_txt {
None => {
if i < 3 {
return Err(WavefrontLoadError::FormatError { msg: "Face has less than 3 vertices" });
}
break;
},
Some(val) => val
};
let mut face_index_split: Split<&str> = face_index_txt.split("/");
let mut face_index_ref = [0, 0, 0u32];
for j in 0..3 {
let face_index_comp = face_index_split.next()
.ok_or(WavefrontLoadError::FormatError { msg: "Face index ref must contain 3 indices" })?;
if j == 1 && face_index_comp.len() == 0 {
continue;
}
face_index_ref[j] = face_index_comp.trim().parse()?;
}
let mut new_vert = Vertex {
position: vertex_info.get((face_index_ref[0] - 1) as usize)
.ok_or(WavefrontLoadError::BadIndexError { msg: "Vertex index does not exist" })?.clone(),
normal: normal_info.get((face_index_ref[2] - 1) as usize)
.ok_or(WavefrontLoadError::BadIndexError { msg: "Normal index does not exist" })?.clone(),
texcoords: [0., 0.]
};
if face_index_ref[1] > 0 {
new_vert.texcoords = texcoord_info.get((face_index_ref[1] - 1) as usize)
.ok_or(WavefrontLoadError::BadIndexError { msg: "Texcoord index does not exist" })?.clone();
}
vertices.push(new_vert);
face_indices.push((vertices.len() - 1) as u32);
}
if split.next().is_some() {
return Err(WavefrontLoadError::FormatError { msg: "Face has more than 4 vertices" });
}
indices.extend_from_slice(&[face_indices[2], face_indices[1], face_indices[0]]);
if face_indices.len() == 4 {
indices.extend_from_slice(&[face_indices[3], face_indices[2], face_indices[0]]);
}
Ok(())
}
fn load_mtl(display: &Display, obj_split: &mut Split<char>, obj_parent_dir: &Path,
textures: &mut HashMap<String, Texture2d>, mtl_map: &mut HashMap<String, MtlInfo>) -> Result<(), WavefrontLoadError> {
let filename = obj_split.next()
.ok_or(WavefrontLoadError::FormatError { msg: "mtllib does not have filename" })?;
let f = File::open(obj_parent_dir.join(filename.trim()).as_path())?;
let mut f = BufReader::new(f);
let mut line = String::new();
let mut current_name: Option<String> = None;
while f.read_line(&mut line)? != 0 {
let mut split = line.split(' ');
let key = split.next().unwrap().trim();
if key == "newmtl" {
let name = split.next()
.ok_or(WavefrontLoadError::FormatError { msg: "newmtl does not have a name" })?.to_string();
mtl_map.insert(name.clone(), Default::default());
current_name = Some(name);
}
if let Some(name) = current_name.as_ref() {
let mtl = mtl_map.get_mut(name).unwrap();
match key {
"map_Kd" => {
let img_filename = split.next()
.ok_or(WavefrontLoadError::FormatError { msg: "map_Kd does not have a filename" })?.to_string();
if !textures.contains_key(&img_filename) {
let img_path = obj_parent_dir.join(img_filename.trim());
let txt = load_texture(display, img_path.as_path(), true)?;
textures.insert(img_filename.clone(), txt);
}
mtl.diffuse_texture = Some(img_filename.clone());
},
"Kd" => {
for i in 0..3 {
mtl.color[i] = split.next()
.ok_or(WavefrontLoadError::FormatError { msg: "Kd is missing a component" })?
.trim().parse()?;
}
},
&_ => ()
}
}
line.clear();
}
Ok(())
}
fn process_obj(display: Option<&&Display>, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>,
current_mtl: &Option<MtlInfo>, quadoctree: Option<&mut &mut QuadOctreeNode>, lights: Option<&mut &mut HashMap<String, Light>>,
misc_objs: Option<&mut &mut HashMap<String, [f32; 3]>>,
o_name: &mut Option<String>, result: &mut BTreeMap<String, ObjDef>) -> Result<(), WavefrontLoadError> {
let mesh_type = if o_name.as_ref().unwrap().starts_with(COLLISION_PREFIX) {
MeshType::Collision
} else if o_name.as_ref().unwrap().starts_with(LIGHT_PREFIX) {
MeshType::Light
} else if o_name.as_ref().unwrap().starts_with(TERRAIN_PREFIX) {
MeshType::Terrain
} else if o_name.as_ref().unwrap().starts_with(MISC_PREFIX) {
MeshType::Misc
} else { MeshType::Normal };
if let Some(display) = display {
if MeshType::Normal == mesh_type || MeshType::Terrain == mesh_type {
let mut def = load_data_to_gpu(*display, &vertices, &indices);
def.material = Some(current_mtl.as_ref().unwrap().clone());
result.insert(o_name.as_ref().unwrap().clone(), def);
}
}
if MeshType::Misc == mesh_type {
if let Some(misc_objs) = misc_objs {
misc_objs.insert(o_name.as_ref().unwrap().to_string(), vertices[0].position);
}
}
if MeshType::Terrain == mesh_type || MeshType::Collision == mesh_type {
if let Some(quadoctree) = quadoctree {
add_obj_to_quadoctree(&mut (**quadoctree), &vertices, &indices, MeshType::Collision == mesh_type)?;
}
}
if MeshType::Light == mesh_type {
if let Some(lights) = lights {
lights.insert(o_name.as_ref().unwrap().to_string().trim().to_string(),
Light { position: vertices[0].position, ..Default::default() });
}
}
vertices.clear();
indices.clear();
*o_name = None;
Ok(())
}
pub fn load_obj(filename: &str, app_id: &str, display: Option<&Display>, mut textures: Option<&mut HashMap<String, Texture2d>>,
scale: &[f32; 3], mut quadoctree: Option<&mut QuadOctreeNode>, mut lights: Option<&mut HashMap<String, Light>>,
mut misc_objs: Option<&mut HashMap<String, [f32; 3]>>) -> Result<BTreeMap<String, ObjDef>, WavefrontLoadError> {
let path = find_asset(filename, app_id);
let f = File::open(path.as_path())?;
let mut f = BufReader::new(f);
let mut line = String::new();
let mut vertex_info: Vec<[f32; 3]> = Vec::new();
let mut normal_info: Vec<[f32; 3]> = Vec::new();
let mut texcoord_info: Vec<[f32; 2]> = Vec::new();
let mut vertices: Vec<Vertex> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let mut mtl_map: HashMap<String, MtlInfo> = HashMap::new();
let mut current_mtl: Option<MtlInfo> = None;
let mut current_o_name: Option<String> = None;
let mut result: BTreeMap<String, ObjDef> = BTreeMap::new();
while f.read_line(&mut line)? != 0 {
let mut split = line.split(' ');
match split.next().unwrap() {
"mtllib" => {
if let Some(display) = display.as_ref() {
let parent_dir = path.parent().unwrap();
load_mtl(*display, &mut split, &parent_dir, *textures.as_mut().unwrap(), &mut mtl_map)?;
}
},
"usemtl" => {
if display.is_some() {
let mtl_name = split.next()
.ok_or(WavefrontLoadError::FormatError { msg: "usemtl does not have a name" })?;
current_mtl = Some(mtl_map.get(mtl_name)
.ok_or(WavefrontLoadError::FormatError { msg: "Material does not exist" })?.clone());
}
},
"v" => vertex_info.push(parse_vertex_or_normal(&mut split, scale)?),
"vn" => normal_info.push(parse_vertex_or_normal(&mut split, &[1., 1., 1.])?),
"vt" => texcoord_info.push(parse_texcoords(&mut split)?),
"f" => parse_face(&mut split, &vertex_info, &normal_info, &texcoord_info, &mut vertices,
&mut indices)?,
"o" => {
if current_o_name.is_some() {
process_obj(display.as_ref(), &mut vertices, &mut indices, ¤t_mtl,
quadoctree.as_mut(), lights.as_mut(), misc_objs.as_mut(), &mut current_o_name, &mut result)?;
}
current_o_name = Some(split.next()
.ok_or(WavefrontLoadError::FormatError { msg: "o does not have a name" })?.to_string());
}
&_ => ()
}
line.clear();
}
if current_o_name.is_some() {
process_obj(display.as_ref(), &mut vertices, &mut indices, ¤t_mtl,
quadoctree.as_mut(), lights.as_mut(), misc_objs.as_mut(), &mut current_o_name, &mut result)?;
}
Ok(result)
}