model-converter/src/model/mod.rs

567 lines
18 KiB
Rust

//! Module containg the struct for storing 3D model
pub mod face;
pub mod material;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::rc::Rc;
use glium::VertexBuffer;
use glium::texture::SrgbTexture2d;
use parser::{parse_file, ParserError};
use model::face::{FaceVertex, Face};
use model::material::Material;
use math::vector::{Vector2, Vector3};
use math::bounding_box::BoundingBox3;
use renderer::Renderer;
#[derive(Copy, Clone, Debug, PartialEq)]
/// A raw vertex, with its 3D coordinates, texture coordinates and normals.
pub struct Vertex {
vertex: [f64; 3],
tex_coords: [f64; 2],
normal: [f64; 3],
}
implement_vertex!(Vertex, vertex, tex_coords, normal);
/// A part of a 3D model.
///
/// It contains a list of faces that correspond to the same material.
pub struct Part {
/// Name of the corresponding material.
pub material_name: Option<String>,
/// List of all the faces in the part.
faces: Vec<Face>,
/// VertexBuffer for rendering.
vertex_buffer: Option<VertexBuffer<Vertex>>,
/// True if the faces have been changed since the last time the buffer was built.
needs_update: bool,
}
impl Part {
/// Creates a empty part from its material name.
pub fn new(material_name: Option<String>) -> Part {
Part {
material_name: material_name,
faces: vec![],
vertex_buffer: None,
needs_update: true,
}
}
/// Adds a face to the part.
///
/// The face material will be changed according to the part material.
pub fn add_face(&mut self, f: Face) {
let mut f = f;
f.material_name = self.material_name.clone();
self.faces.push(f);
self.needs_update = true;
}
/// Returns a reference to the vertex buffer.
pub fn vertex_buffer(&self) -> &Option<VertexBuffer<Vertex>> {
&self.vertex_buffer
}
/// Returns a reference to the faces.
pub fn faces(&self) -> &Vec<Face> {
&self.faces
}
}
/// A 3D model.
///
/// It contains all the data needed to do a rendering.
pub struct Model {
/// List of all vertices.
pub vertices: Vec<Vector3<f64>>,
/// List of all texture coordinates.
pub texture_coordinates: Vec<Vector2<f64>>,
/// List of all normals.
pub normals: Vec<Vector3<f64>>,
/// Map associating the name of a material to the real material.
pub materials: HashMap<String, Material>,
/// Map associating the name of a texture and the real texture.
pub textures: HashMap<String, Rc<SrgbTexture2d>>,
/// The list of parts of the model.
pub parts: Vec<Part>,
/// The list of faces, in the order of the file
pub faces: Vec<Face>,
/// The part that is currently selected.
current_part_index: Option<usize>,
}
/// Error when adding a face to a model.
pub enum ModelError {
/// Index (first usize) received but greater than size (second usize).
IndexError(usize, usize),
}
impl Model {
/// Creates an empty model.
pub fn new() -> Model {
Model {
vertices: vec![],
texture_coordinates: vec![],
normals: vec![],
materials: HashMap::new(),
parts: vec![],
faces: vec![],
current_part_index: None,
textures: HashMap::new(),
}
}
/// Copies the materials and textures from the other model into self.
pub fn merge_material_and_textures(&mut self, other: &Model) {
// Merge the materials
for (name, material) in &other.materials {
let entry = self.materials.entry(name.clone());
if let Entry::Occupied(_) = entry {
eprintln!("Warning: materials merged but sharing the same name");
// Return error
} else {
entry.or_insert(material.clone());
}
}
// Merge the textures
for (name, texture) in &other.textures {
let entry = self.textures.entry(name.clone());
if let Entry::Occupied(_) = entry {
eprintln!("Warning: textures merged but sharing the same name");
// return Error;
} else {
entry.or_insert(texture.clone());
}
}
}
/// Merges the model passed as parameter into new parts of the current model.
pub fn merge_in_new_parts(&mut self, other: Model) {
let mut other = other;
// Merge the materials
for (name, material) in other.materials {
let entry = self.materials.entry(name);
if let Entry::Occupied(_) = entry {
eprintln!("Warning: materials merged but sharing the same name");
// Return error
} else {
entry.or_insert(material);
}
}
// Merge the textures
for (name, texture) in other.textures {
let entry = self.textures.entry(name);
if let Entry::Occupied(_) = entry {
eprintln!("Warning: textures merged but sharing the same name");
// return Error;
} else {
entry.or_insert(texture);
}
}
let vertex_offset = self.vertices.len();
let tex_coord_offset = self.texture_coordinates.len();
let normal_offset = self.normals.len();
let face_offset = self.faces.len();
// Merge the elements
self.vertices.append(&mut other.vertices);
self.texture_coordinates.append(&mut other.texture_coordinates);
self.normals.append(&mut other.normals);
// Merge the parts and faces
for part in &mut other.parts {
let mut new_part = Part::new(part.material_name.clone());
for face in &mut part.faces {
for fv in &mut [&mut face.a, &mut face.b, &mut face.c] {
fv.vertex += vertex_offset;
if let Some(tex_coord) = fv.texture_coordinate {
fv.texture_coordinate = Some(tex_coord + tex_coord_offset);
}
if let Some(normal) = fv.normal {
fv.normal = Some(normal + normal_offset);
}
}
if let Some(id) = face.id {
face.id = Some(id + face_offset);
}
face.material_name = new_part.material_name.clone();
new_part.faces.push(face.clone());
self.faces.push(face.clone());
}
new_part.needs_update = true;
self.parts.push(new_part);
}
}
/// Merges the model passed as parameter into existing parts of the current model.
pub fn merge_in_existing_parts(&mut self, other: Model) {
let mut other = other;
// Merge the materials
for (name, material) in other.materials {
let entry = self.materials.entry(name);
if let Entry::Occupied(_) = entry {
eprintln!("Warning: materials merged but sharing the same name");
// Return error
} else {
entry.or_insert(material);
}
}
// Merge the textures
for (name, texture) in other.textures {
let entry = self.textures.entry(name);
if let Entry::Occupied(_) = entry {
eprintln!("Warning: textures merged but sharing the same name");
// return Error;
} else {
entry.or_insert(texture);
}
}
let vertex_offset = self.vertices.len();
let tex_coord_offset = self.texture_coordinates.len();
let normal_offset = self.normals.len();
let face_offset = self.faces.len();
// Merge the elements
self.vertices.append(&mut other.vertices);
self.texture_coordinates.append(&mut other.texture_coordinates);
self.normals.append(&mut other.normals);
// Merge the parts and faces
for part in &mut other.parts {
self.change_part(part.material_name.clone());
let mut new_faces = vec![];
{
let new_part = self.current_part_mut().unwrap();
for face in &mut part.faces {
for fv in &mut [&mut face.a, &mut face.b, &mut face.c] {
fv.vertex += vertex_offset;
if let Some(tex_coord) = fv.texture_coordinate {
fv.texture_coordinate = Some(tex_coord + tex_coord_offset);
}
if let Some(normal) = fv.normal {
fv.normal = Some(normal + normal_offset);
}
}
if let Some(id) = face.id {
face.id = Some(id + face_offset);
}
face.material_name = new_part.material_name.clone();
new_part.faces.push(face.clone());
new_faces.push(face);
}
}
for face in new_faces {
self.faces.push(face.clone());
}
}
}
/// Creates a model from a file.
pub fn from(path: &str) -> Result<Model, ParserError> {
parse_file(path)
}
/// Computes the area of a 3D face of the model.
pub fn face_area(&self, f: &Face) -> f64 {
self.face_vertex_area(&f.a, &f.b, &f.c)
}
/// Computes the 3D area of a face from 3 face vertices.
pub fn face_vertex_area(&self, a: &FaceVertex, b: &FaceVertex, c: &FaceVertex) -> f64 {
let v1 = self.vertices[a.vertex];
let v2 = self.vertices[b.vertex];
let v3 = self.vertices[c.vertex];
Vector3::area(v1, v2, v3)
}
/// Returns the name of the current material, i.e. the material corresponding to the current
/// part.
pub fn current_material_name(&self) -> Option<String> {
if let Some(p) = self.current_part() {
p.material_name.clone()
} else {
None
}
}
/// Changes the part in order to select a specific material.
///
/// If no part with such material exists, it will be created.
pub fn change_part(&mut self, material_name: Option<String>) {
self.current_part_index = None;
for (index, part) in self.parts.iter().enumerate() {
if part.material_name == material_name {
self.current_part_index = Some(index);
}
}
if self.current_part_index.is_none() {
// Create a new part
self.create_part(material_name.clone());
self.current_part_index = Some(self.parts.len() - 1);
}
}
/// Creates a part with a specific material.
pub fn create_part(&mut self, material_name: Option<String>) {
self.parts.push(Part::new(material_name));
}
/// Returns the current selected part.
///
/// Returns None if the model contains no part.
pub fn current_part(&self) -> Option<&Part> {
if let Some(index) = self.current_part_index {
Some(&self.parts[index])
} else {
None
}
}
/// Returns the current selected part.
///
/// Returns None if the model contains no part.
pub fn current_part_mut(&mut self) -> Option<&mut Part> {
if let Some(index) = self.current_part_index {
Some(&mut self.parts[index])
} else {
None
}
}
/// Adds a face to the current part of the model.
///
/// Returns a ModelError if the face has out of bounds face vertices.
pub fn add_face(&mut self, f: Face) -> Result<(), ModelError> {
// Check that the indices are correct
for face_vertex in [&f.a, &f.b, &f.c].iter() {
if face_vertex.vertex >= self.vertices.len() {
return Err(ModelError::IndexError(face_vertex.vertex, self.vertices.len()));
}
if let Some(tc) = face_vertex.texture_coordinate {
if tc >= self.texture_coordinates.len() {
return Err(ModelError::IndexError(tc, self.texture_coordinates.len()));
}
}
if let Some(n) = face_vertex.normal {
if n >= self.normals.len() {
return Err(ModelError::IndexError(n, self.normals.len()));
}
}
}
let material_name = self.current_material_name();
self.change_part(material_name.clone());
// Add the id and the material name
let mut f = f;
f.id = Some(self.faces.len());
f.material_name = material_name;
self.current_part_mut().unwrap().add_face(f.clone());
self.faces.push(f);
Ok(())
}
/// Computes the bounding box of the model.
///
/// This function works on the vertices. If there is an unused vertex, it will still count in
/// the bounding box.
pub fn bounding_box(&self) -> BoundingBox3<f64> {
let mut bbox = BoundingBox3::new(self.vertices[0].clone(), self.vertices[0].clone());
for vertex in &self.vertices {
bbox.add_point(&vertex);
}
bbox
}
/// Computes stats on the area of the faces
///
/// Returns a pair containing the average and the standard deviation.
pub fn get_area_stats(&self) -> (f64, f64) {
let mut areas = vec![];
let mut avg = 0.0;
for (index, face) in self.faces.iter().enumerate() {
let area = Vector3::area(
self.vertices[face.a.vertex],
self.vertices[face.b.vertex],
self.vertices[face.c.vertex],
);
areas.push((index, area));
avg += area;
}
avg /= areas.len() as f64;
let mut sd = 0.0;
for &(_, area) in &areas {
sd += (area - avg) * (area - avg);
}
sd = (sd / areas.len() as f64).sqrt();
(avg, sd)
}
/// Centers and scales the model from a bounding box.
pub fn center_and_scale_from_box(&mut self, bbox: &BoundingBox3<f64>) {
// let bbox = self.bounding_box();
let center = (bbox.min() + bbox.max()) / 2.0;
let size = (bbox.max() - bbox.min()).norm();
for vertex in &mut self.vertices {
*vertex -= center;
*vertex /= size;
}
}
/// Centers and scales the model from its own bounding box.
pub fn center_and_scale(&mut self) {
let bbox = self.bounding_box();
self.center_and_scale_from_box(&bbox);
}
/// Creates vertex buffers for each part of the model.
pub fn build_vertex_buffers(&mut self, renderer: &Renderer) {
for part in &mut self.parts {
Model::build_vertex_buffer_for_part(
&self.vertices,
&self.texture_coordinates,
&self.normals,
part,
renderer);
}
}
/// Creates only non-existant vertex buffers, doesn't update the old ones.
pub fn build_new_vertex_buffers(&mut self, renderer: &Renderer) {
for part in &mut self.parts {
if part.vertex_buffer.is_none() || part.needs_update {
Model::build_vertex_buffer_for_part(
&self.vertices,
&self.texture_coordinates,
&self.normals,
part,
renderer);
}
}
}
/// Creates a vertex buffer that can be rendered for a part of the model.
pub fn build_vertex_buffer_for_part(
vertices: &Vec<Vector3<f64>>,
texture_coordinates: &Vec<Vector2<f64>>,
normals: &Vec<Vector3<f64>>,
part: &mut Part,
renderer: &Renderer) {
let mut vertex_buffer = vec![];
for face in part.faces() {
for &&v in &[&face.a, &face.b, &face.c] {
let vertex = vertices[v.vertex].into();
let tex_coord = if let Some(tex_index) = v.texture_coordinate {
texture_coordinates[tex_index].into()
} else {
[0.0, 0.0]
};
let normal = if let Some(normal_index) = v.normal {
normals[normal_index].into()
} else {
[0.0, 0.0, 0.0]
};
vertex_buffer.push(Vertex {
vertex: vertex,
tex_coords: tex_coord,
normal: normal,
});
}
}
part.vertex_buffer = Some(renderer.build_vertex_buffer(&vertex_buffer));
part.needs_update = false;
}
/// Builds the textures of all materials.
pub fn build_textures(&mut self, renderer: &Renderer) {
for (_, material) in self.materials.clone() {
self.build_texture_for_material(&material, renderer);
}
}
/// Builds the SrgbTextures for rendering.
pub fn build_texture_for_material(&mut self, material: &Material, renderer: &Renderer) {
if let Some(path) = material.textures.get("map_Kd") {
let texture = renderer.make_texture(path);
// Don't need to insert multiple times the same texture
self.textures.entry(path.to_owned()).or_insert(Rc::new(texture));
};
}
/// Returns the rendering texture.
pub fn get_texture_by_name(&self, name: &str) -> Option<&Rc<SrgbTexture2d>> {
self.textures.get(name)
}
/// Returns the material.
pub fn get_material_by_name(&self, name: &str) -> Option<&Material> {
self.materials.get(name)
}
/// Returns a ref mut to the table of textures.
pub fn textures_mut(&mut self) -> &mut HashMap<String, Rc<SrgbTexture2d>> {
&mut self.textures
}
}