use std::collections::HashMap;
use std::io::{BufRead, BufReader};
use std::iter;
use wgpu::{BufferUsages, Device};
use wgpu::util::{BufferInitDescriptor, DeviceExt};
use crate::asset::AssetManagerRc;
use crate::model::{InstShaderType, Mesh, PrimitiveStateType, Submesh, VertexPosNormal, VertexShaderType};
type MeshIndex = u32;
pub struct Obj;
impl Obj {
pub fn open<S: AsRef<str>>(asset_mgr: AssetManagerRc, device: &Device, name: S, submesh_infos: &[(&str, &InstShaderType)]) -> Mesh { assert!(!submesh_infos.is_empty());
let asset_file = asset_mgr.open_or_err(&format!("/obj/{}.obj", name.as_ref()));
let mut reader = BufReader::new(asset_file.read_or_err());
let mut submeshes: Box<[_]> = iter::repeat_with(|| None).take(submesh_infos.len()).collect(); let mut inst_index_opt = None;
let mut base_pos: MeshIndex = 0;
let mut base_normal: MeshIndex = 0;
let mut poss = Vec::new();
let mut normals = Vec::new();
let mut index_map = HashMap::new();
let mut base_vertex: MeshIndex = 0;
let mut base_index: MeshIndex = 0;
let mut vertexes = Vec::new();
let mut indexes = Vec::new();
loop {
let mut finish = || { if let Some(inst_index) = inst_index_opt {
let submesh_info: &(&str, &InstShaderType) = &submesh_infos[inst_index]; let submesh = Submesh::new(base_index, indexes.len().try_into().unwrap(), base_vertex.try_into().unwrap(), PrimitiveStateType::TriangleList, submesh_info.1.clone());
submeshes[inst_index] = Some(submesh);
inst_index_opt = None;
}
};
let mut buf = String::new();
let r = reader.read_line(&mut buf).unwrap();
if r == 0 { finish();
break;
}
let line = buf.trim_end();
let (op, line) = line.split_once(' ').unwrap();
if op == "o" {
finish();
base_pos += TryInto::<MeshIndex>::try_into(poss.len()).unwrap();
base_normal += TryInto::<MeshIndex>::try_into(normals.len()).unwrap();
base_vertex = TryInto::<MeshIndex>::try_into(vertexes.len()).unwrap();
base_index = TryInto::<MeshIndex>::try_into(indexes.len()).unwrap();
poss.clear();
normals.clear();
index_map.clear();
if let Some((i, _)) = submesh_infos.iter().enumerate().find(|(_, submesh_info)| submesh_info.0 == line) {
assert!(submeshes[i].is_none()); inst_index_opt = Some(i);
}
} else if inst_index_opt.is_some() {
match op {
"v" => { let nums: Box<[f32]> = line.split(' ').map(|num| num.parse().unwrap()).collect();
assert!(nums.len() == 3);
poss.push([nums[0], nums[1], nums[2]]);
},
"vn" => { let nums: Box<[f32]> = line.split(' ').map(|num| num.parse().unwrap()).collect();
assert!(nums.len() == 3);
normals.push([nums[0], nums[1], nums[2]]);
},
"f" => { let face_indexes: Box<[_]> = line.split(' ').collect();
assert!(face_indexes.len() == 3);
for face_index in face_indexes {
let nums: Box<[_]> = face_index.split('/').collect();
assert!(nums.len() == 3);
let nums: Box<[_]> = [nums[0], nums[2]].iter().map(|num| num.parse::<MeshIndex>().unwrap() - 1).collect();
let (pos_index, normal_index) = (nums[0] - base_pos, nums[1] - base_normal);
let index_key = (pos_index, normal_index);
let index = if let Some(index) = index_map.get(&index_key) {
*index
} else {
let index: u16 = (vertexes.len() - base_vertex as usize).try_into().unwrap(); index_map.insert(index_key, index);
let pos = poss[pos_index as usize];
let normal = normals[normal_index as usize];
let vertex = VertexPosNormal {
pos,
normal,
};
vertexes.push(vertex);
index
};
indexes.push(index);
}
},
_ => (),
}
} else {
match op {
"v" => base_pos += 1,
"vn" => base_normal += 1,
_ => (),
}
}
}
let vertex_buf = device.create_buffer_init(&BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&vertexes),
usage: BufferUsages::VERTEX,
});
let index_buf = device.create_buffer_init(&BufferInitDescriptor {
label: None,
contents: bytemuck::cast_slice(&indexes),
usage: BufferUsages::INDEX,
});
let submeshes = submeshes.into_iter().map(|submesh| submesh.expect("Incomplete mesh")).collect();
Mesh::new(vertex_buf, index_buf, VertexShaderType::PosNormal, submeshes)
}
}