567 lines
18 KiB
Rust
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
|
|
}
|
|
}
|