WIP: obj loader mostly done

This commit is contained in:
2023-10-21 04:19:29 +03:00
committed by Illusion
parent 1dd39d9f70
commit 421c7f5db3
9 changed files with 583 additions and 270 deletions
+31
View File
@@ -0,0 +1,31 @@
mod obj;
use std::path::PathBuf;
use crate::{mesh::Vertex, aabb::AABB};
#[derive(Debug)]
pub struct ObjMesh {
pub name: String,
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
}
#[derive(Debug)]
pub struct Object {
pub name: String,
pub meshes: Vec<ObjMesh>,
pub aabb: AABB
}
pub fn load_from_file(path: &PathBuf) -> Result<Object, Box<dyn std::error::Error>> {
// FIXME: don't explode if to_str() fails
let path_str = path.to_str().unwrap();
let file = std::fs::File::open(path_str)?;
// TODO: support both stl and obj based on file extension
// if no extension, then test for binary STL magic bytes
// if no magic bytes, then try to guess based on the first line of text in the file
let obj = obj::load_obj(file)?;
Ok(obj)
}
+248
View File
@@ -0,0 +1,248 @@
use std::io::{BufReader, BufRead};
use crate::{mesh::Vertex, aabb::AABB, importer::ObjMesh, importer::Object};
const BUF_CAP: usize = 1024 * 128; // 128 Kilobytes
enum Token {
Object,
Group,
Vertex,
Normal,
TexCoord,
Face,
Point,
Line,
SmoothShading,
MaterialLib,
MaterialUsage,
}
impl Token {
fn from_str<'a>(s: &'a str) -> Option<Token> {
match s {
"o" => Some(Token::Object),
"g" => Some(Token::Group),
"v" => Some(Token::Vertex),
"vn" => Some(Token::Normal),
"vt" => Some(Token::TexCoord),
"f" => Some(Token::Face),
"p" => Some(Token::Point),
"l" => Some(Token::Line),
"s" => Some(Token::SmoothShading),
"mtllib" => Some(Token::MaterialLib),
"usemtl" => Some(Token::MaterialUsage),
_ => None,
}
}
}
pub fn load_obj(file: std::fs::File) -> Result<Object, Box<dyn std::error::Error>> {
let now = std::time::Instant::now();
let reader = BufReader::with_capacity(BUF_CAP, file);
let mut object_name = String::new();
let mut current_mesh_name = String::new();
let mut temp_vertices = Vec::new();
let mut vertices = Vec::new();
let mut normals = Vec::new();
let mut indices_counter: u32 = 0;
let mut indices = Vec::new();
let mut tex_coords = Vec::new();
let mut meshes = Vec::new();
let mut min_aabb = glm::vec3(std::f32::MAX, std::f32::MAX, std::f32::MAX);
let mut max_aabb = glm::vec3(std::f32::MIN, std::f32::MIN, std::f32::MIN);
for line in reader.lines() {
let line = line?;
// skip empty lines and comments
if line.is_empty() || line.chars().nth(0).is_some_and(|c| c == '#') {
continue;
}
let mut iter = line.split_ascii_whitespace();
let first = iter.next();
if let Some(token) = first {
match Token::from_str(token) {
Some(Token::Object) => {
let name = {
if current_mesh_name.is_empty() {
object_name.clone()
} else {
current_mesh_name.clone()
}
};
if !vertices.is_empty() {
meshes.push(ObjMesh{
name,
vertices: vertices.clone(),
indices: indices.clone(),
});
}
vertices.clear();
indices.clear();
indices_counter = 0;
object_name = iter.next().unwrap_or("").to_string();
}
Some(Token::Group) => {
let name = {
if current_mesh_name.is_empty() {
object_name.clone()
} else {
current_mesh_name.clone()
}
};
if !vertices.is_empty() {
meshes.push(ObjMesh{
name,
vertices: vertices.clone(),
indices: indices.clone(),
});
}
vertices.clear();
indices.clear();
indices_counter = 0;
current_mesh_name = iter.next().unwrap_or("default_mesh").to_string();
}
Some(Token::Vertex) => {
let mut iter = iter
.take(3)
.map(|i| i.parse::<f32>().unwrap());
let x = iter.next().unwrap();
let y = iter.next().unwrap();
let z = iter.next().unwrap();
temp_vertices.push(glm::vec3(x, y, z));
if x < min_aabb.x {
min_aabb.x = x;
}
if y < min_aabb.y {
min_aabb.y = y;
}
if z < min_aabb.z {
min_aabb.z = z;
}
if x > max_aabb.x {
max_aabb.x = x;
}
if y > max_aabb.y {
max_aabb.y = y;
}
if z > max_aabb.z {
max_aabb.z = z;
}
}
Some(Token::Normal) => {
let mut iter = iter
.take(3)
.map(|i| i.parse::<f32>().unwrap());
let x = iter.next().unwrap();
let y = iter.next().unwrap();
let z = iter.next().unwrap();
normals.push(glm::vec3(x, y, z));
}
Some(Token::TexCoord) => {
let mut iter = iter
.take(2)
.map(|i| i.parse::<f32>().unwrap());
let u = iter.next().unwrap();
let v = iter.next().unwrap();
tex_coords.push(glm::vec2(u, v));
}
Some(Token::Face) => {
// TODO: vertex indices can be negative
let face = iter.collect::<Vec<_>>();
let mut calculated_normal = glm::vec3(0.0, 0.0, 0.0);
if normals.is_empty() {
calculated_normal = glm::normalize(glm::cross(
temp_vertices[face[1].parse::<i32>().unwrap() as usize - 1] - temp_vertices[face[0].parse::<i32>().unwrap() as usize - 1],
temp_vertices[face[2].parse::<i32>().unwrap() as usize - 1] - temp_vertices[face[0].parse::<i32>().unwrap() as usize - 1]
));
}
for (i, vert) in face.iter().enumerate() {
if vert.contains("//") {
let mut it = vert.split("//");
let vert = it.next().unwrap().parse::<i32>().unwrap() - 1;
let normal = it.next().unwrap().parse::<i32>().unwrap() - 1;
vertices.push(Vertex{
position: *temp_vertices.get(vert as usize).unwrap(),
normal: *normals.get(normal as usize).unwrap(),
tex_coords: glm::vec2(0.0, 0.0)
});
} else if vert.contains("/") {
let mut it = vert.split("/");
let vertex = it.next().unwrap().parse::<i32>().unwrap() - 1;
let t_coords = it.next().unwrap().parse::<i32>().unwrap() - 1;
let normal = it.next().unwrap().parse::<i32>().unwrap() - 1;
vertices.push(Vertex{
position: *temp_vertices.get(vertex as usize).unwrap(),
normal: *normals.get(normal as usize).unwrap(),
tex_coords: *tex_coords.get(t_coords as usize).unwrap()
});
} else {
let vert = vert.parse::<i32>().unwrap() - 1;
vertices.push(Vertex{
position: *temp_vertices.get(vert as usize).unwrap(),
normal: calculated_normal,
tex_coords: glm::vec2(0.0, 0.0),
});
}
// 2 triangles per face
if i < face.len() - 2 {
indices.push(indices_counter);
indices.push(indices_counter + i as u32 + 1);
indices.push(indices_counter + i as u32 + 2);
}
}
indices_counter += face.len() as u32;
}
Some(Token::MaterialLib) => {
// TODO: handle material file
}
Some(Token::MaterialUsage) => {
// TODO: handle material usage
// this can be used multiple times before f is mentioned
// to apply the material to those faces after it
}
_ => {},
}
}
}
let elapsed = now.elapsed();
println!("Loaded in {}ms", elapsed.as_millis());
let mesh_name = {
if current_mesh_name.is_empty() && !object_name.is_empty() {
object_name.clone()
} else if !current_mesh_name.is_empty() {
current_mesh_name.clone()
} else {
"default_mesh".to_string()
}
};
meshes.push(ObjMesh{
name: mesh_name,
vertices: vertices.clone(),
indices: indices.clone(),
});
let aabb = AABB::new(min_aabb, max_aabb);
Ok(Object{
name: object_name,
meshes,
aabb
})
}
+27
View File
@@ -0,0 +1,27 @@
struct STLHeader {
header: [u8; 80],
num_triangles: u32,
}
struct Vertex {
x: f32,
y: f32,
z: f32,
}
struct Triangle {
normal: [f32; 3],
vertices: [Vertex; 3],
attribute_byte_count: u16,
}
struct STL {
header: STLHeader,
triangles: Vec<Triangle>,
}
impl STL {
pub fn new(stl: Vec<u8>) -> () {
// TODO:
}
}