feo-oop-engine 0.0.5

An Object Oriented game engine for rust.
Documentation
//! A GameObject that can be built from an obj file.
//! 
//! TODO: explain OOP here
//! 
use {
    super::{
        GameObject,
        camera::Camera,
        light::Light,
    },
    crate::{
        registration::{
            relation::{ParentWrapper, Child, Parent},
            named::Named,
            id::{
                ID
            }
        },
        scripting::{
            Script,
            executor::Spawner,
            Scriptable, 
            globals::{
                EngineGlobals, 
                Global
            }
        },
        graphics::{
            Drawable,
            draw_pass_manager::DrawPassManager,
            lighting_pass_manager::LightingPassManager,
        },
        components::{
            Vertex,
            TextureIndex,
            Normal
        },
        term_ui,
        event::UserEvent,
        components::{material::Material, triangle_mesh::TriangleMesh}
    },
    feo_math::{
        utils::space::Space, 
        rotation::quaternion::Quaternion,
        linear_algebra::vector3::Vector3
    },
    std::{
        any::Any, 
        collections::HashMap, 
        fs, 
        io::stdout, 
        mem, 
        sync::{Arc, RwLock}
    },
    futures::executor::block_on,
    vulkano::{
        descriptor::{
            descriptor_set::PersistentDescriptorSet,
        },
        sync::GpuFuture
    },
    winit::event::Event
};

#[derive(Scriptable, GameObject, Drawable, Child, Parent, Named)] // import
pub struct Obj {
    pub id: ID,
    pub name: String,
    pub parent: ParentWrapper,

    pub visible: bool,

    pub subspace: Space, // note is the subspace within the parent space

    pub triangle_mesh: Vec<Arc<TriangleMesh>>,
    // pub material: Option<Material>, // object does not have material triangle mesh does

    pub script: Option<Box<Script<Self>>>,

    pub children: Vec<Arc<RwLock<dyn GameObject>>>,
}

impl std::fmt::Debug for Obj {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Obj")
            .field("id", &self.id)
            .field("name", &self.name)
            .field("parent", &self.parent)
            .field("visible", &self.visible)
            .field("subspace", &self.subspace)
            .field("triangle_mesh", &self.triangle_mesh)
            .field("script", &self.script)
            .field("children", &self.children).finish()
    }
}

impl Clone for Obj {
    fn clone(&self) -> Self {
        let id = self.id.get_system().take();
        Obj{
            id,
            name: self.name.clone(),
            parent: self.parent.clone(),
            visible: self.visible,
            subspace: self.subspace,
            triangle_mesh: self.triangle_mesh.clone(),
            script: self.script.clone(),
            children: self.children.clone().into_iter().map(|_child| {
                // Dangerous
                todo!();
            }).collect::<Vec<Arc<RwLock<dyn GameObject>>>>(),
        }
    }
}

impl PartialEq for Obj { // auto-generate this somehow
    fn eq(&self, other: &Self) -> bool {
        self.get_id() == other.get_id()
    }
}

// TODO: rename to builders and build gameobjects with them
impl Obj {
    #[allow(clippy::too_many_arguments, clippy::or_fun_call)]
    pub fn new_empty(
            name: Option<&str>,

            parent: Option<Arc<RwLock<dyn GameObject>>>, 

            position: Option<Vector3<f32>>, 
            rotation: Option<Quaternion<f32>>,
            scale_factor: Option<Vector3<f32>>,

            visible: bool,

            engine_globals: EngineGlobals,

            script: Option<Box<Script<Self>>>) -> Arc<RwLock<Self>> {
        let id = engine_globals.id_system.take();

        return Arc::new(RwLock::new( Obj {
            name: name.unwrap_or((String::from("obj_") + id.to_string().as_str()).as_str()).to_owned(),
            id,
            parent: match parent {
                Some(game_object) => {
                    ParentWrapper::GameObject(game_object)
                },
                None => {
                    ParentWrapper::Scene(engine_globals.scene)
                }
            },

            visible,

            subspace: Space::new(position, rotation, scale_factor),

            triangle_mesh: Vec::new(),

            script,

            children: Vec::new()
        }));
    }

    #[allow(clippy::too_many_arguments, clippy::or_fun_call)]
    pub fn from_triangle_mesh_vec(
            name: Option<&str>, 
            triangle_mesh_vec: Vec<Arc<TriangleMesh>>,

            parent: Option<Arc<RwLock<dyn GameObject>>>, 

            position: Option<Vector3<f32>>, 
            rotation: Option<Quaternion<f32>>,
            scale_factor: Option<Vector3<f32>>,

            visible: bool,

            engine_globals: EngineGlobals,

            script: Option<Box<Script<Self>>>) -> Arc<RwLock<Self>> {
        let id = engine_globals.id_system.take();
        return Arc::new(RwLock::new( Obj{
            name: name.unwrap_or((String::from("obj_") + id.to_string().as_str()).as_str()).to_owned(),
            id,
            parent: match parent {
                Some(game_object) => {
                    ParentWrapper::GameObject(game_object)
                },
                None => {
                    ParentWrapper::Scene(engine_globals.scene)
                }
            },

            visible,

            subspace: Space::new(position, rotation, scale_factor),

            triangle_mesh: triangle_mesh_vec,

            script,

            children: Vec::new(),
        }));
    }
    
    #[allow(clippy::too_many_arguments)]
    pub fn from_obj<'a>( // TODO: cut file into groups pass groups into triangle mesh for parsing
            name: Option<&str>, 
            path: &str, 

            parent: Option<Arc<RwLock<dyn GameObject>>>, 

            position: Option<Vector3<f32>>, 
            rotation: Option<Quaternion<f32>>,
            scale_factor: Option<Vector3<f32>>,
            
            visible: bool,

            engine_globals: EngineGlobals,

            script: Option<Box<Script<Self>>>) -> Result<Arc<RwLock<Self>>, &'a str>{
        
        //   Data Pools   //

        let mut vertex_positions= Vec::new();
        let mut texture_indices = Vec::new();
        let mut normals = Vec::new();
        
        let mut mtls_hashmap: HashMap<String, (Arc<Material>, Box<dyn GpuFuture>)> = HashMap::new();

        //   Read In String Data   //
        
        let content = fs::read_to_string(path)
            .unwrap_or_else(|_| panic!("Something went wrong when trying to read {}.", path));
        let mut last_block = Box::new(Vec::new());
        let lines: Vec<(&str, Vec<&str>)> = content.lines().filter_map(|line| {
            if !line.is_empty() {
                let mut e = line.split_whitespace();
                let ty: &str = e.next().unwrap();
                match &*ty {

                    "" | "#" | "s" | "l" => None,

                    //   Vertex Data   //

                    "v" => {
                        vertex_positions.push(Box::new(Vertex::new(
                            e.next().unwrap().parse::<f32>().unwrap(),
                            e.next().unwrap().parse::<f32>().unwrap(),
                            e.next().unwrap().parse::<f32>().unwrap()
                        )));

                        None
                    },
                    "vt" => {
                        texture_indices.push(Box::new(TextureIndex::new(
                            e.next().unwrap().parse::<f32>().unwrap(),
                            e.next().unwrap().parse::<f32>().unwrap()
                        )));

                        None
                    },
                    "vn" => {
                        normals.push(Box::new(Normal::new(
                            e.next().unwrap().parse::<f32>().unwrap(),
                            e.next().unwrap().parse::<f32>().unwrap(),
                            e.next().unwrap().parse::<f32>().unwrap()
                        )));
                        
                        None
                    },

                    //   Materials   //

                    "mtllib" => {
                        let files = e.fold(String::new(), |mut a, b| {
                            a.reserve(b.len() + 1);
                            a.push_str(b);
                            a.push(' ');
                            a
                        });

                        let files = files.trim_end_matches(".mtl ");

                        files.split(".mtl ").for_each(|file| {
                            let file = "/".to_owned() + file + ".mtl";
                            let path = path.rsplitn(2, '/').nth(1).unwrap();
                            let path = path.to_owned() + file.as_str();

                            mtls_hashmap.extend(Material::from_mtllib(&path, engine_globals.clone().queue));
                        });

                        None
                    },

                    
                    //   Faces and Materials   //

                    "f" | "usemtl" => {
                        last_block.push(line);
                        None
                    },
                    
                    //   Groupings   //

                    "g" | "o" => {
                        let tmp_block = *last_block.clone();
                        last_block = Box::new(Vec::new());
                        Some((line, tmp_block))
                    },

                    //   Other   //

                    a => {println!("\n\nA: {} \n", a); None}
                }
            } else {
                None
            }
        }).collect();
        
        //   Create A New Container For The Model   //

        let name = name.unwrap_or_else(|| path.split('/').last().unwrap()).to_string();
        let this = Obj::new_empty(
            Some(name.as_str()), 
            parent, 
            position, 
            rotation, 
            scale_factor, 
            visible, 
            engine_globals.clone(), 
            script
        );

        //   Group Data   //

        // The current group/container being written to
        let mut current_group: Option<Arc<RwLock<dyn GameObject>>> = None;

        //   Loading Bar   //

        // standard output handle
        let mut stdout = stdout();

        // name of file and current group to be displayed
        let file_name = path.split('/').last().unwrap();
        let current_group_name: Option<&str> = None;

        // line number and total number of lines
        let mut line_n: usize = 0;
        let file_size = lines.len();

        // how often to update the loading bar
        let update = file_size / 100; // or just use 500 very small impact

        lines.into_iter().for_each(|line| {

            //   Loading Bar   //
            
            line_n += 1;
            
            // Get the terminal width
            let terminal_width = terminal_size::terminal_size().unwrap().0.0 as usize;

            // Don't always draw. unless on the final stretch
            let draw = update == 0 || line_n % update == 4; // || line_n >= file_size - update;

            // store the current_group name so it is 'static
            let future_s_current_group_name = &*current_group_name.unwrap_or("");

            // create the loading bar future
            let future = async {
                if draw {
                    term_ui::progress_bar(&mut stdout, file_name, future_s_current_group_name, line_n, file_size, terminal_width).await
                }
            };
            
            let mut e = line.0.split_whitespace();
            #[allow(clippy::or_fun_call)]
            let ty: &str = e.next().ok_or(format!("error on line {} of {}", line_n, path).as_str()).unwrap();


            // replaces flush
            if let Some(group) = current_group.clone() {
                let mut group_write_lock = group.write().unwrap();
                // create the triangle mesh and add it to the current group
                let triangle_mesh = TriangleMesh::from_obj_block(&line.1, &mut mtls_hashmap, (&vertex_positions, &texture_indices, &normals), engine_globals.queue.clone()).unwrap();
                
                group_write_lock.add_triangle_mesh( 
                    Arc::new(triangle_mesh)
                ).unwrap();
            }

            match &*ty {
                "g" => {
                    current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
                    let mut groups = e.collect::<Vec<&str>>();
                    while let Some(group_name) = groups.pop() {
                        let group = current_group.clone().unwrap();
                        let mut wlock_current_group = group.write().unwrap();
                        if let Ok(group) = wlock_current_group.get_child_by_name(group_name) {
                            drop(wlock_current_group);
                            current_group = Some(group);
                        } else {
                            let new_group = Obj::new_empty(
                                Some(group_name),
                                current_group.clone(),
                                None,
                                None,
                                None,
                                true,
                                engine_globals.clone(),
                                None
                            );
                            wlock_current_group.add_child(new_group.clone());
                            drop(wlock_current_group);
                            current_group = Some(new_group);
                        }
                    }
                },
                "o" => {
                    current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
                    
                    let object_name = e.next().expect("The o tag does not permit default/no names");
                    let new_object = Obj::new_empty(
                        Some(object_name),
                        current_group.clone(),
                        None,
                        None,
                        None,
                        true,
                        engine_globals.clone(),
                        None
                    );

                    current_group.clone().unwrap().write().unwrap().add_child(new_object.clone());
                    current_group = Some(new_object);
                },

                _ => unreachable!()
            }

            block_on(future);
        });

        let group = match current_group {
            Some(group) => group.clone(),
            None => this.clone()
        };

        let mut group_write_lock = group.write().unwrap();
        // create the triangle mesh and add it to the current group
        let triangle_mesh = TriangleMesh::from_obj_block(&last_block, &mut mtls_hashmap, (&vertex_positions, &texture_indices, &normals), engine_globals.queue.clone()).unwrap();
        
        group_write_lock.add_triangle_mesh( 
            Arc::new(triangle_mesh)
        ).unwrap();

        Ok(this)
    }
    
    // pub fn from_obj_old<'a>( // couple mistakes I caught are still in here
    //         name: Option<&str>, 
    //         path: &str, 

    //         parent: Option<Arc<RwLock<dyn GameObject>>>, 

    //         position: Option<Vector3<f32>>, 
    //         rotation: Option<Quaternion<f32>>,
    //         scale_factor: Option<Vector3<f32>>,
            
    //         visible: bool,

    //         engine_globals: EngineGlobals,

    //         script: Option<Box<Script<Self>>>) -> Result<Arc<RwLock<Self>>, &'a str>{
        
    //     //   Read In String Data   //
        
    //     let content = fs::read_to_string(path)
    //         .expect(&format!("Something went wrong when trying to read {}.", path));
    //     let lines = content.lines();

    //     //   Create A New Container For The Model   //

    //     let name = name.unwrap_or(path.split('/').last().unwrap()).to_string();
    //     let this = Obj::new_empty(
    //         Some(name.as_str()), 
    //         parent, 
    //         position, 
    //         rotation, 
    //         scale_factor, 
    //         visible, 
    //         engine_globals.clone(), 
    //         script
    //     );

    //     //   Group Data   //

    //     // The current group/container being written to
    //     let mut current_group = None;

    //     // Ordered mesh data
    //     let mut ordered_vertices = Vec::new();
    //     let mut ordered_normals = Vec::new();
    //     let mut ordered_texture_indices = Vec::new();

    //     let mut current_material: Option<Arc<Material>> = None;
        
    //     /// fn that flushes the ordered_vertices, ordered_normals, and ordered_texture_indices buffers 
    //     /// and writes the data into the current group
    //     /// Note it does not reset the current group
    //     fn flush(
    //             current_group: Option<Arc<RwLock<dyn GameObject>>>, 

    //             ordered_vertices: &mut Vec<Vertex>, 
    //             ordered_normals: &mut Vec<Normal>, 
    //             ordered_texture_indices: &mut Vec<TextureIndex>, 

    //             current_material: &mut Option<Arc<Material>>,
                
    //             engine_globals: EngineGlobals) {
    //         if let Some(group) = current_group.clone() {
    //             let group = group.clone();
    //             let mut group_write_lock = group.write().unwrap();
    //             // create the triangle mesh and add it to the current group
    //             let triangle_mesh = TriangleMesh::new(
    //                     ordered_vertices.clone(),
    //                     ordered_normals.clone(), 
    //                     ordered_texture_indices.clone(),
    //                     current_material.clone().unwrap_or(Arc::new(Material::default())),
    //                     engine_globals.queue
    //                 );
                
    //             group_write_lock.add_triangle_mesh( 
    //                 Arc::new(triangle_mesh)
    //             ).unwrap();

    //             // Reset the ordered vecs
    //             *ordered_vertices = Vec::new();
    //             *ordered_normals = Vec::new();
    //             *ordered_texture_indices = Vec::new();

    //             // reset the current material
    //             *current_material = None;
    //         }
    //     }

    //     //   Loading Bar   //

    //     // standard output handle
    //     let mut stdout = stdout();

    //     // name of file and current group to be displayed
    //     let file_name = path.split('/').last().unwrap();
    //     let current_group_name: Option<&str> = None;

    //     // line number and total number of lines
    //     let mut line_n: usize = 0;
    //     let file_size = lines.clone().count();

    //     // how often to update the loading bar
    //     let update = file_size / 100; // or just use 500 very small impact

    //     //   Data Pools   //

    //     let mut vertex_positions: Vec<Vertex> = Vec::new();
    //     let mut texture_indices: Vec<TextureIndex> = Vec::new();
    //     let mut normals: Vec<Normal> = Vec::new();
        
    //     let mut mtls_hashmap: HashMap<String, (Arc<Material>, Box<dyn GpuFuture>)> = HashMap::new();


        
    //     lines.for_each(|line| {

    //         //   Loading Bar   //
            
    //         line_n += 1;
            
    //         // Get the terminal width
    //         let terminal_width = terminal_size::terminal_size().unwrap().0.0 as usize;

    //         // Don't always draw. unless on the final stretch
    //         let draw = update == 0 || line_n % update == 4; // || line_n >= file_size - update;

    //         // store the current_group name so it is 'static
    //         let future_s_current_group_name = &*current_group_name.unwrap_or("");

    //         // create the loading bar future
    //         let future = async {
    //             if draw {
    //                 term_ui::progress_bar(&mut stdout, file_name, future_s_current_group_name, line_n, file_size, terminal_width).await
    //             }
    //         };
            
    //         if !line.is_empty() {
    //             let mut e = line.split_whitespace();
    //             let ty: &str = e.next().ok_or(format!("error on line {} of {}", line_n, path).as_str()).unwrap();
    //             match &*ty {

    //                 //   Groupings   //

    //                 "g" => {
    //                     flush(current_group.clone(), &mut ordered_vertices, &mut ordered_normals, &mut ordered_texture_indices, &mut current_material, engine_globals.clone());
    //                     current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
    //                     let mut groups = e.collect::<Vec<&str>>();
    //                     while let Some(group_name) = groups.pop() {
    //                         let group = current_group.clone().unwrap();
    //                         let mut wlock_current_group = group.write().unwrap();
    //                         if let Ok(group) = wlock_current_group.get_child_by_name(group_name) {
    //                             drop(wlock_current_group);
    //                             current_group = Some(group);
    //                         } else {
    //                             let new_group = Obj::new_empty(
    //                                 Some(group_name),
    //                                 current_group.clone(),
    //                                 None,
    //                                 None,
    //                                 None,
    //                                 true,
    //                                 engine_globals.clone(),
    //                                 None
    //                             );
    //                             wlock_current_group.add_child(new_group.clone());
    //                             drop(wlock_current_group);
    //                             current_group = Some(new_group);
    //                         }
    //                     }
    //                 },
    //                 "o" => {
    //                     flush(current_group.clone(), &mut ordered_vertices, &mut ordered_normals, &mut ordered_texture_indices, &mut current_material, engine_globals.clone());
    //                     current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
                        
    //                     let object_name = e.next().expect("The o tag does not permit default/no names");
    //                     let new_object = Obj::new_empty(
    //                         Some(object_name),
    //                         current_group.clone(),
    //                         None,
    //                         None,
    //                         None,
    //                         true,
    //                         engine_globals.clone(),
    //                         None
    //                     );

    //                     current_group.clone().unwrap().write().unwrap().add_child(new_object.clone());
    //                     current_group = Some(new_object);
    //                 },

    //                 //   Vertex Data   //

    //                 "v" => {
    //                     vertex_positions.push(Vertex::new(
    //                         e.next().unwrap().parse::<f32>().unwrap(),
    //                         e.next().unwrap().parse::<f32>().unwrap(),
    //                         e.next().unwrap().parse::<f32>().unwrap()
    //                     ));
    //                 },
    //                 "vt" => {
    //                     texture_indices.push(TextureIndex::new(
    //                         e.next().unwrap().parse::<f32>().unwrap(),
    //                         e.next().unwrap().parse::<f32>().unwrap()
    //                     ));
    //                 },
    //                 "vn" => {
    //                     normals.push(Normal::new(
    //                         e.next().unwrap().parse::<f32>().unwrap(),
    //                         e.next().unwrap().parse::<f32>().unwrap(),
    //                         e.next().unwrap().parse::<f32>().unwrap()
    //                     ));
    //                 },

    //                 //   Faces   //

    //                 "f" => {
    //                     let mut tris = Vec::new();
    //                     let mut i = 0;
    //                     for coord in &mut e {
    //                         i += 1;
    //                         if i > 3{ // not perfect but good enough for now
    //                             tris.push(tris[0]);
    //                             tris.push(tris[i - 2]);
    //                         }
    //                         tris.push(coord);
    //                     }

    //                     let mut vertex_fmt: i8 = -1;
    //                     let mut developing_normal: Vec<Vertex> = Vec::new();
    //                     for raw in tris{
    //                         let part = raw.split('/').collect::<Vec<&str>>();
                            
    //                         if vertex_fmt != part.len() as i8 {
    //                             if vertex_fmt == -1 {
    //                                 vertex_fmt = part.len() as i8;
    //                             }else {
    //                                 panic! ("Inconsistent face vertex format in {}.", path)
    //                             }
    //                         }
                            
    //                         let position = vertex_positions[part[0].parse::<usize>().unwrap() - 1_usize];

    //                         let mut texture_index = TextureIndex::new(0.0, 0.0);

    //                         if vertex_fmt > 1 && !part[1].is_empty() {
    //                             texture_index = texture_indices[part[1].parse::<usize>().unwrap() - 1_usize];
    //                         }
    //                         // I could have sworn I have separated these multiple times now
    //                         if developing_normal.is_empty() && vertex_fmt == 3 && !part[2].is_empty() { // a false second case is a result of improper formatting
    //                             ordered_normals.push(normals[part[2].parse::<usize>().unwrap() - 1_usize]);
    //                         } else {
    //                             developing_normal.push(position);
    //                         }

    //                         ordered_vertices.push(position);
    //                         ordered_texture_indices.push(texture_index);
    //                     }

    //                     if developing_normal.len() > 2 {
    //                         let normal = Normal::calculate_normal(&developing_normal[0], &developing_normal[1], developing_normal.last().unwrap());

    //                         for _ in 0..developing_normal.len() {
    //                             ordered_normals.push(normal);
    //                         }
    //                     }
    //                 },
                    
    //                 //   Materials   //

    //                 "mtllib" => {
    //                     let files = e.fold(String::new(), |mut a, b| {
    //                         a.reserve(b.len() + 1);
    //                         a.push_str(b);
    //                         a.push(' ');
    //                         a
    //                     });

    //                     let files = files.trim_end_matches(".mtl ");

    //                     files.split(".mtl ").for_each(|file| {
    //                         let file = "/".to_owned() + file + ".mtl";
    //                         let path = path.rsplitn(2, '/').nth(1).unwrap();
    //                         let path = path.to_owned() + file.as_str();

    //                         mtls_hashmap.extend(Material::from_mtllib(&path, engine_globals.clone().queue));
    //                     });
    //                 },
    //                 "usemtl" => {
    //                     let key = e.next().expect(format!("formatting error in {}", path).as_str());
    //                     let (cm, fut ) = mtls_hashmap.remove(key).unwrap();
    //                     current_material = Some(cm.clone());
    //                     if fut.queue().is_some() {
    //                         let _ = Arc::new(fut.then_signal_fence_and_flush().unwrap()).wait(None); // for now state does not matter                            
    //                     }
    //                     mtls_hashmap.insert(key.to_string(), (cm, sync::now(engine_globals.queue.device().clone()).boxed()));
                        
    //                 },

    //                 //   Other   //

    //                 "#" => (),
    //                 "" => (),
    //                 &_ => {
    //                     //panic!(format!("unsupported type on line {} of {}", line_n, path)); 
    //                 }
                    
    //                 //   TODO: Other Geometry   //

    //                 #[allow(unreachable_patterns)] // fr now just ignore it TODO: fix

    //                 "line" => {
    //                     /* remember that a normal of 0.0, 0.0, 0.0 is perfect because it is visible from any angle */
    //                     todo!();
    //                 },
    //             };
    //         }
            
    //         block_on(future);
    //     });

    //     flush(current_group, &mut ordered_vertices, &mut ordered_normals, &mut ordered_texture_indices, &mut current_material, engine_globals);

    //     Ok(this)
    // }
}