Skip to content

Commit

Permalink
Rework mesh loading and add normal calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
AmionSky committed Nov 13, 2022
1 parent 76c188f commit b9edbcb
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 53 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bevy_app = "0.9"
bevy_asset = "0.9"
bevy_render = "0.9"
bevy_utils = "0.9"
bevy_math = "0.9"

anyhow = "1.0"
thiserror = "1.0"
Expand Down
176 changes: 123 additions & 53 deletions src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use bevy_render::{
mesh::{Indices, Mesh},
render_resource::PrimitiveTopology,
};
use bevy_utils::BoxedFuture;
use bevy_utils::{BoxedFuture, HashMap};
use obj::raw::{object::Polygon, RawObj};
use thiserror::Error;

#[derive(Default)]
Expand All @@ -29,8 +30,8 @@ impl AssetLoader for ObjLoader {
pub enum ObjError {
#[error("Invalid OBJ file: {0}")]
Gltf(#[from] obj::ObjError),
#[error("Unknown vertex format")]
UnknownVertexFormat,
#[error("Mesh is not triangulated.")]
NonTriangulatedMesh,
}

async fn load_obj<'a, 'b>(
Expand All @@ -43,73 +44,142 @@ async fn load_obj<'a, 'b>(
Ok(())
}

type VertexKey = (usize, usize, usize);

struct MeshIndices {
indices: Vec<u32>,
saved: HashMap<VertexKey, u32>,
next: u32,
}

impl MeshIndices {
pub fn new(capacity: usize) -> Self {
Self {
indices: Vec::with_capacity(capacity),
saved: HashMap::with_capacity(capacity),
next: 0,
}
}

pub fn insert<F: FnOnce()>(&mut self, key: VertexKey, create_vertex: F) {
// Check if the vertex is already saved
match self.saved.get(&key) {
Some(index) => self.indices.push(*index), // If saved, just use the existing index
None => {
// Save the index to both the indices and saved
self.indices.push(self.next);
self.saved.insert(key, self.next);
// Increment next index
self.next += 1;
// Create a vertex externally
create_vertex()
}
}
}
}

impl From<MeshIndices> for Vec<u32> {
fn from(val: MeshIndices) -> Self {
val.indices
}
}

fn load_obj_from_bytes(bytes: &[u8], mesh: &mut Mesh) -> Result<(), ObjError> {
let raw = obj::raw::parse_obj(bytes)?;
let vertcount = raw.polygons.len() * 3;

let mut indices = MeshIndices::new(vertcount);

let mut vertex_position = Vec::with_capacity(vertcount);
let mut vertex_normal = Vec::with_capacity(vertcount);
let mut vertex_texture = Vec::with_capacity(vertcount);

// Get the most complete vertex representation
// 1 => Position
// 2 => Position, Normal
// 3 => Position, Normal, Texture
let mut pnt = 4;
for polygon in &raw.polygons {
use obj::raw::object::Polygon;
match polygon {
Polygon::P(_) => pnt = std::cmp::min(pnt, 1),
Polygon::PT(_) => pnt = std::cmp::min(pnt, 1),
Polygon::PN(_) => pnt = std::cmp::min(pnt, 2),
Polygon::PTN(_) => pnt = std::cmp::min(pnt, 3),
Polygon::P(poly) if poly.len() == 3 => {
let normal = calculate_normal(&raw, poly);

for ipos in poly {
indices.insert((*ipos, 0, 0), || {
vertex_position.push(convert_position(&raw, *ipos));
vertex_normal.push(normal);
});
}
}
Polygon::PT(poly) if poly.len() == 3 => {
let triangle: Vec<usize> = poly.iter().map(|(ipos, _)| *ipos).collect();
let normal = calculate_normal(&raw, &triangle);

for (ipos, itex) in poly {
indices.insert((*ipos, 0, *itex), || {
vertex_position.push(convert_position(&raw, *ipos));
vertex_normal.push(normal);
vertex_texture.push(convert_texture(&raw, *itex));
});
}
}
Polygon::PN(poly) if poly.len() == 3 => {
for (ipos, inorm) in poly {
indices.insert((*ipos, *inorm, 0), || {
vertex_position.push(convert_position(&raw, *ipos));
vertex_normal.push(convert_normal(&raw, *inorm));
});
}
}
Polygon::PTN(poly) if poly.len() == 3 => {
for (ipos, itex, inorm) in poly {
indices.insert((*ipos, *inorm, *itex), || {
vertex_position.push(convert_position(&raw, *ipos));
vertex_normal.push(convert_normal(&raw, *inorm));
vertex_texture.push(convert_texture(&raw, *itex));
});
}
}
_ => return Err(ObjError::NonTriangulatedMesh),
}
}

match pnt {
1 => {
let obj: obj::Obj<obj::Position, u32> = obj::Obj::new(raw)?;
set_position_data(mesh, obj.vertices.iter().map(|v| v.position).collect());
set_normal_data(mesh, obj.vertices.iter().map(|_| [0., 0., 0.]).collect());
set_uv_data(mesh, obj.vertices.iter().map(|_| [0., 0.]).collect());
set_mesh_indices(mesh, obj);
}
2 => {
let obj: obj::Obj<obj::Vertex, u32> = obj::Obj::new(raw)?;
set_position_data(mesh, obj.vertices.iter().map(|v| v.position).collect());
set_normal_data(mesh, obj.vertices.iter().map(|v| v.normal).collect());
set_uv_data(mesh, obj.vertices.iter().map(|_| [0., 0.]).collect());
set_mesh_indices(mesh, obj);
}
3 => {
let obj: obj::Obj<obj::TexturedVertex, u32> = obj::Obj::new(raw)?;
set_position_data(mesh, obj.vertices.iter().map(|v| v.position).collect());
set_normal_data(mesh, obj.vertices.iter().map(|v| v.normal).collect());
set_uv_data(
mesh,
obj.vertices
.iter()
// Flip UV for correct values
.map(|v| [v.texture[0], 1.0 - v.texture[1]])
.collect(),
);
set_mesh_indices(mesh, obj);
}
_ => return Err(ObjError::UnknownVertexFormat),
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_position);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normal);
if !vertex_texture.is_empty() {
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vertex_texture);
}

mesh.set_indices(Some(Indices::U32(indices.into())));

Ok(())
}

fn set_position_data(mesh: &mut Mesh, data: Vec<[f32; 3]>) {
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, data);
fn convert_position(raw: &RawObj, index: usize) -> [f32; 3] {
let position = raw.positions[index];
[position.0, position.1, position.2]
}

fn set_normal_data(mesh: &mut Mesh, data: Vec<[f32; 3]>) {
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, data);
fn convert_normal(raw: &RawObj, index: usize) -> [f32; 3] {
let normal = raw.normals[index];
[normal.0, normal.1, normal.2]
}

fn set_uv_data(mesh: &mut Mesh, data: Vec<[f32; 2]>) {
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, data);
fn convert_texture(raw: &RawObj, index: usize) -> [f32; 2] {
let tex_coord = raw.tex_coords[index];
// Flip UV for correct values
[tex_coord.0, 1.0 - tex_coord.1]
}

fn set_mesh_indices<T>(mesh: &mut Mesh, obj: obj::Obj<T, u32>) {
mesh.set_indices(Some(Indices::U32(
obj.indices.iter().map(|i| *i as u32).collect(),
)));
/// Simple and inaccurate normal calculation
fn calculate_normal(raw: &RawObj, polygon: &[usize]) -> [f32; 3] {
use bevy_math::Vec3;

// Extract triangle
let triangle: Vec<Vec3> = polygon
.iter()
.map(|index| Vec3::from(convert_position(raw, *index)))
.collect();

// Calculate normal
let v1 = triangle[1] - triangle[0];
let v2 = triangle[2] - triangle[0];
let n = v1.cross(v2);

n.to_array()
}

0 comments on commit b9edbcb

Please sign in to comment.