model-converter/src/model.rs

395 lines
10 KiB
Rust

//! Module containg the struct for storing 3D model
use std::collections::HashMap;
use std::fmt;
use parser::{parse, ParserError};
use math::vector::{Vector2, Vector3};
use math::bounding_box::BoundingBox3;
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Vertex {
vertex: [f64; 3],
tex_coords: [f64; 2],
normal: [f64; 3],
}
implement_vertex!(Vertex, vertex, tex_coords, normal);
#[derive(Copy, Clone, PartialEq)]
/// The indices needed for each vertex of a face.
pub struct FaceVertex {
/// The index of the vertex.
pub vertex: usize,
/// The index of the texture coordinate, None if there is no texture coordinate.
pub texture_coordinate: Option<usize>,
/// The index of the normal, None if there is no normal.
pub normal: Option<usize>,
}
impl FaceVertex {
/// Creates a new face vertex from its attributes.
pub fn new(vertex: usize, texture_coordinate: Option<usize>, normal: Option<usize>) -> FaceVertex {
FaceVertex {
vertex: vertex,
texture_coordinate: texture_coordinate,
normal: normal,
}
}
}
impl fmt::Debug for FaceVertex {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let texture_coordinate = if let Some(tc) = self.texture_coordinate {
tc.to_string()
} else {
"".to_owned()
};
let normal = if let Some(n) = self.normal {
n.to_string()
} else {
"".to_owned()
};
write!(formatter, "{}/{}/{}", self.vertex, texture_coordinate, normal)
}
}
#[derive(Clone, PartialEq, Debug)]
/// A 3D Face.
///
/// It contains exactly 3 face vertices, and the name of the corresponding material.
pub struct Face {
/// The first vertex of the face.
pub a: FaceVertex,
/// The second vertex of the face.
pub b: FaceVertex,
/// The third vertex of the face.
pub c: FaceVertex,
/// The name of the material, None if no material is specified.
pub material_name: Option<String>,
/// Id of the face, number of the face in the original file.
pub id: Option<usize>,
}
impl Face {
/// Creates a new face from its attributes.
///
/// Material name defaults to None.
pub fn new(a: FaceVertex, b: FaceVertex, c: FaceVertex) -> Face {
Face {
a: a,
b: b,
c: c,
material_name: None,
id: None,
}
}
}
/// A 3D material.
pub struct Material {
/// The name of the material.
pub name: String,
/// Map linking each texture map to its file path.
pub textures: HashMap<String, String>,
/// Instructions that are unknown.
///
/// They might be necessary for the export though.
pub unknown_instructions: Vec<String>,
}
impl Material {
/// Creates a material from its name, without texture.
pub fn new(name: &str) -> Material {
Material {
name: name.to_owned(),
textures: HashMap::new(),
unknown_instructions: vec![],
}
}
/// Adds a unknown instruction into the material.
pub fn add_unknown_instruction(&mut self, instruction: String) {
self.unknown_instructions.push(instruction);
}
}
/// 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.
pub faces: Vec<Face>,
}
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![],
}
}
/// 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);
}
pub fn build_shape(&self, vertices: &Vec<Vector3<f64>>, tex_coords: &Vec<Vector2<f64>>, normals: &Vec<Vector3<f64>>) -> Vec<Vertex> {
let mut shape = vec![];
for face in &self.faces {
for &&v in &[&face.a, &face.b, &face.c] {
shape.push(Vertex {
vertex: vertices[v.vertex].into(),
tex_coords: tex_coords[v.texture_coordinate.unwrap()].into(),
normal: normals[v.normal.unwrap()].into(),
});
}
}
shape
}
}
/// 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>,
/// 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,
}
}
/// Creates a model from a file.
pub fn from(path: &str) -> Result<Model, ParserError> {
parse(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)
}
}