Inital commit coming from dash-3d
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
//! 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, 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user