miye 0.1.2

A simple, lightweight .obj model renderer.
Documentation
use std::{
    path::{Path, PathBuf},
    str::FromStr,
};
use wgpu::util::DeviceExt;

pub fn load_model(file_path: &str, state: &crate::state::State, position: glam::Vec3) -> Model {
    let (models, materials) = tobj::load_obj(
        file_path,
        &tobj::LoadOptions {
            triangulate: true,
            single_index: true,
            ..Default::default()
        },
    )
    .unwrap();
    let materials = materials.unwrap();

    let file_path = Path::new(file_path);
    Model::new(
        models,
        file_path,
        &state.device,
        &state.queue,
        materials,
        position,
    )
}

#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
    position: [f32; 3],
    tex_coords: [f32; 2],
}

impl Vertex {
    pub fn desc() -> wgpu::VertexBufferLayout<'static> {
        wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &[
                wgpu::VertexAttribute {
                    offset: 0,
                    shader_location: 0,
                    format: wgpu::VertexFormat::Float32x3,
                },
                wgpu::VertexAttribute {
                    offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
                    shader_location: 1,
                    format: wgpu::VertexFormat::Float32x2,
                },
            ],
        }
    }
}

#[derive(Debug)]
pub struct Model {
    pub mesh: Vec<Mesh>,
    pub material: Vec<Material>,
}

#[derive(Debug)]
pub struct Material {
    pub name: String,
    pub texture: crate::texture::Texture,
    pub bind_group: wgpu::BindGroup,
}

#[derive(Debug)]
pub struct Mesh {
    pub name: String,
    pub vertex_buffer: wgpu::Buffer,
    pub index_buffer: wgpu::Buffer,
    pub num_elements: u32,
    pub material: usize,
}

impl Model {
    pub fn new(
        models: Vec<tobj::Model>,
        file_path: &Path,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        model_materials: Vec<tobj::Material>,
        position: glam::Vec3,
    ) -> Self {
        let mut materials = Vec::new();
        for m in model_materials {
            let texture_filename = m.diffuse_texture.unwrap();
            let texture_path = PathBuf::from_str(&texture_filename).unwrap();

            let image_bytes = std::fs::read(
                file_path
                    .parent()
                    .unwrap()
                    .join(texture_path.file_name().unwrap()),
            )
            .expect("Failed to read file");

            let texture =
                crate::texture::Texture::from_bytes(device, queue, &image_bytes, "Texture");

            let texture_bind_group_layout =
                device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                    label: Some("Texture Bind Group Layout"),
                    entries: &[
                        wgpu::BindGroupLayoutEntry {
                            binding: 0,
                            visibility: wgpu::ShaderStages::FRAGMENT,
                            ty: wgpu::BindingType::Texture {
                                sample_type: wgpu::TextureSampleType::Float { filterable: true },
                                view_dimension: wgpu::TextureViewDimension::D2,
                                multisampled: false,
                            },
                            count: None,
                        },
                        wgpu::BindGroupLayoutEntry {
                            binding: 1,
                            visibility: wgpu::ShaderStages::FRAGMENT,
                            ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                            count: None,
                        },
                    ],
                });

            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Texture Bind Group"),
                layout: &texture_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(&texture.view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&texture.sampler),
                    },
                ],
            });

            materials.push(Material {
                name: m.name,
                texture,
                bind_group,
            });
        }

        let meshes = models
            .into_iter()
            .map(|m| {
                let vertices = (0..m.mesh.positions.len() / 3)
                    .map(|i| Vertex {
                        position: [
                            m.mesh.positions[i * 3] + position.x,
                            m.mesh.positions[i * 3 + 1] + position.y,
                            m.mesh.positions[i * 3 + 2] + position.z,
                        ],
                        tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]],
                    })
                    .collect::<Vec<_>>();

                let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                    label: Some(&format!("{:?} Vertex Buffer", file_path.to_str().unwrap())),
                    contents: bytemuck::cast_slice(&vertices),
                    usage: wgpu::BufferUsages::VERTEX,
                });
                let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                    label: Some(&format!("{:?} Index Buffer", file_path.to_str().unwrap())),
                    contents: bytemuck::cast_slice(&m.mesh.indices),
                    usage: wgpu::BufferUsages::INDEX,
                });

                Mesh {
                    name: file_path.to_str().unwrap().to_string(),
                    vertex_buffer,
                    index_buffer,
                    num_elements: m.mesh.indices.len() as u32,
                    material: m.mesh.material_id.unwrap_or(0),
                }
            })
            .collect::<Vec<_>>();

        Model {
            mesh: meshes,
            material: materials,
        }
    }
}