422 lines
11 KiB
Rust
422 lines
11 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)]
|
|
/// 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);
|
|
|
|
#[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);
|
|
}
|
|
|
|
/// Creates a shape that can be rendered.
|
|
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] {
|
|
let vertex = vertices[v.vertex].into();
|
|
let tex_coord = if let Some(tex_index) = v.texture_coordinate {
|
|
tex_coords[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]
|
|
};
|
|
|
|
shape.push(Vertex {
|
|
vertex: vertex,
|
|
tex_coords: tex_coord,
|
|
normal: normal,
|
|
});
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
/// Centers and scales the model.
|
|
pub fn center_and_scale(&mut self) {
|
|
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;
|
|
}
|
|
}
|
|
}
|