wde_renderer/assets/
mesh.rs

1use wde_logger::prelude::*;
2
3use bevy::{
4    asset::{AssetLoader, LoadContext, io::Reader},
5    ecs::system::{
6        SystemParamItem,
7        lifetimeless::{SRes, SResMut}
8    },
9    prelude::*
10};
11use serde::{Deserialize, Serialize};
12use std::{
13    fs::File,
14    io::{BufReader, Error}
15};
16use thiserror::Error;
17use tobj::LoadError;
18use wde_wgpu::{
19    buffer::{Buffer, BufferUsage},
20    vertex::Vertex
21};
22
23use crate::{
24    assets::{GpuBuffer, RenderAssets, SRenderData},
25    core::RenderInstance,
26    utils::{SsboMesh, ssbo_mesh::SsboMeshDescriptor}
27};
28
29use super::asset::{PrepareAssetError, RenderAsset};
30
31/// Utils component that stores a [`Mesh`] asset handle for 3D rendering.
32#[derive(Component, Reflect, Default)]
33pub struct Mesh3d(pub Handle<Mesh>);
34
35#[derive(Clone, Debug)]
36pub struct MeshBbox {
37    pub min: Vec3,
38    pub max: Vec3
39}
40/// Stores a CPU mesh with vertex and index data, along with metadata for GPU allocation.
41/// If loaded from a file, the mesh should have a `.obj` or `.fbx` extension.
42/// The `vertices`, `indices`, and `bbox` fields are expected to be populated by the asset loader; if not loaded from a file, they will default to empty and a degenerate bounding box respectively.
43/// This mesh will be uploaded to the GPU, represented by a [`GpuMesh`] asset.
44#[derive(Asset, TypePath, Clone, Debug)]
45pub struct Mesh {
46    pub label: String,
47
48    pub vertices: Vec<Vertex>,
49    pub indices: Vec<u32>,
50    pub bbox: MeshBbox,
51
52    /// If true, the vertices and indices will automatically be placed in the SSBO mesh buffers [`SsboMesh`] for GPU access; if false, separate vertex and index buffers will be created with `VERTEX` and `INDEX` usage respectively. Defaults to true.
53    pub use_ssbo: bool
54}
55
56/// Represents a GPU mesh resource allocated from a CPU [`Mesh`] asset.
57/// If the mesh was prepared with `use_ssbo = true`, the vertex and index data will be in the SSBO mesh buffer [`SsboMesh`] and the `vertex_buffer` and `index_buffer` fields will be `None`. Otherwhise, they will contain GPU buffers with the vertex and index data respectively.
58pub struct GpuMesh {
59    pub label: String,
60
61    /// Offset to the vertex buffer in the [`SsboMesh`] if `use_ssbo` is true. 0 otherwise.
62    pub ssbo_first_vertex: u32,
63    /// Offset to the index buffer in the [`SsboMesh`] if `use_ssbo` is true. 0 otherwise.
64    pub ssbo_first_index: u32,
65    pub use_ssbo: bool,
66
67    /// If not using SSBO, the GPU vertex buffer containing `Vertex` data. `None` if `use_ssbo` is true.
68    pub vertex_buffer: Option<Buffer>,
69    /// If not using SSBO, the GPU index buffer containing `u32` index data. `None` if `use_ssbo` is true.
70    pub index_buffer: Option<Buffer>,
71
72    pub index_count: u32,
73    pub bounding_box: MeshBbox
74}
75impl RenderAsset for GpuMesh {
76    type SourceAsset = Mesh;
77    type Params = (
78        SRes<RenderInstance>,
79        SResMut<SsboMeshDescriptor>,
80        SRenderData<SsboMesh>,
81        SRes<RenderAssets<GpuBuffer>>
82    );
83
84    fn prepare(
85        asset: Self::SourceAsset,
86        (render_instance, ssbo_descriptor, ssbo_mesh, gpu_buffers): &mut SystemParamItem<
87            Self::Params
88        >
89    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
90        trace!(asset.label, "Preparing GPU mesh asset.");
91
92        // Get the SSBO mesh resource
93        let ssbo = match ssbo_mesh.iter().next() {
94            Some((_, ssbo)) => ssbo,
95            None => return Err(PrepareAssetError::RetryNextUpdate(asset))
96        };
97
98        // Get the ssbo buffers
99        let (ssbo_vertex_buffer, ssbo_index_buffer) = match (
100            gpu_buffers.get(&ssbo.get_buffer(SsboMesh::VERTEX_BUFFER_ID).unwrap()),
101            gpu_buffers.get(&ssbo.get_buffer(SsboMesh::INDEX_BUFFER_ID).unwrap())
102        ) {
103            (Some(vb), Some(ib)) => (vb, ib),
104            _ => return Err(PrepareAssetError::RetryNextUpdate(asset))
105        };
106
107        // Buffer usage
108        let usage_vertex = if asset.use_ssbo {
109            BufferUsage::COPY_SRC
110        } else {
111            BufferUsage::VERTEX
112        };
113        let usage_index = if asset.use_ssbo {
114            BufferUsage::COPY_SRC
115        } else {
116            BufferUsage::INDEX
117        };
118
119        // Create staging buffers
120        let render_instance = render_instance.0.read().unwrap();
121        let vertex_buffer = Buffer::new(
122            &render_instance,
123            format!("{}-vertex-staging", asset.label).as_str(),
124            std::mem::size_of::<Vertex>() * asset.vertices.len(),
125            usage_vertex,
126            Some(bytemuck::cast_slice(&asset.vertices))
127        );
128        let index_buffer = Buffer::new(
129            &render_instance,
130            format!("{}-indices-staging", asset.label).as_str(),
131            std::mem::size_of::<u32>() * asset.indices.len(),
132            usage_index,
133            Some(bytemuck::cast_slice(&asset.indices))
134        );
135
136        // If not using SSBO, return the buffers directly
137        if !asset.use_ssbo {
138            return Ok(GpuMesh {
139                label: asset.label,
140                ssbo_first_vertex: 0,
141                ssbo_first_index: 0,
142                index_count: asset.indices.len() as u32,
143                bounding_box: asset.bbox,
144                use_ssbo: asset.use_ssbo,
145                vertex_buffer: Some(vertex_buffer),
146                index_buffer: Some(index_buffer)
147            });
148        }
149
150        // Copy to GPU buffers
151        let first_vertex = ssbo_descriptor.vertex_buffer_offset;
152        let first_index = ssbo_descriptor.index_buffer_offset;
153        let vertices_count = asset.vertices.len() as u32;
154        let indices_count = asset.indices.len() as u32;
155
156        // Calculate byte offsets and sizes for buffer copy operations
157        let vertices_offset_bytes = (first_vertex as u64) * (std::mem::size_of::<Vertex>() as u64);
158        let indices_offset_bytes = (first_index as u64) * (std::mem::size_of::<u32>() as u64);
159        let vertices_size_bytes = (vertices_count as u64) * (std::mem::size_of::<Vertex>() as u64);
160        let indices_size_bytes = (indices_count as u64) * (std::mem::size_of::<u32>() as u64);
161
162        ssbo_vertex_buffer.buffer.copy_from_buffer_offset(
163            &render_instance,
164            &vertex_buffer,
165            0,
166            vertices_offset_bytes,
167            vertices_size_bytes
168        );
169        ssbo_descriptor.vertex_buffer_offset += vertices_count;
170
171        ssbo_index_buffer.buffer.copy_from_buffer_offset(
172            &render_instance,
173            &index_buffer,
174            0,
175            indices_offset_bytes,
176            indices_size_bytes
177        );
178        ssbo_descriptor.index_buffer_offset += indices_count;
179
180        Ok(GpuMesh {
181            label: asset.label,
182            ssbo_first_vertex: first_vertex,
183            ssbo_first_index: first_index,
184            index_count: indices_count,
185            bounding_box: asset.bbox,
186            use_ssbo: asset.use_ssbo,
187            vertex_buffer: None,
188            index_buffer: None
189        })
190    }
191
192    fn label(&self) -> &str {
193        &self.label
194    }
195}
196
197/// Settings for loading a mesh asset while creating a [`Mesh`].
198#[derive(Serialize, Deserialize)]
199pub struct MeshLoaderSettings {
200    pub label: String,
201    /// Should the vertices and indices be in the SSBO mesh buffers? Defaults to true. If false, separate vertex and index buffers will be created.
202    pub use_ssbo: bool
203}
204impl Default for MeshLoaderSettings {
205    fn default() -> Self {
206        Self {
207            label: "Unknown Mesh".to_string(),
208            use_ssbo: true
209        }
210    }
211}
212
213#[derive(Debug, Error)]
214pub(crate) enum MeshLoaderError {
215    #[error("Could not load mesh: {0}")]
216    Io(#[from] std::io::Error)
217}
218#[derive(Default, TypePath)]
219pub(crate) struct MeshLoader;
220impl AssetLoader for MeshLoader {
221    type Asset = Mesh;
222    type Settings = MeshLoaderSettings;
223    type Error = MeshLoaderError;
224
225    async fn load(
226        &self,
227        reader: &mut dyn Reader,
228        settings: &Self::Settings,
229        load_context: &mut LoadContext<'_>
230    ) -> Result<Self::Asset, Self::Error> {
231        info!("Loading mesh {}.", load_context.path());
232
233        // Update the label from the path
234        let label = if settings.label.is_empty() {
235            load_context.path().to_string()
236        } else {
237            settings.label.clone()
238        };
239
240        // Read the texture data
241        let mut bytes = Vec::new();
242        reader.read_to_end(&mut bytes).await?;
243
244        // Open file
245        let load_res = match tobj::load_obj_buf(
246            &mut BufReader::new(bytes.as_slice()),
247            &tobj::LoadOptions {
248                single_index: true,
249                ..Default::default()
250            },
251            |p| {
252                let f = match File::open(p.file_name().unwrap().to_str().unwrap()) {
253                    Ok(f) => f,
254                    Err(_) => return Err(LoadError::OpenFileFailed)
255                };
256                tobj::load_mtl_buf(&mut BufReader::new(f))
257            }
258        ) {
259            Ok(res) => res,
260            Err(e) => return Err(MeshLoaderError::Io(Error::other(e.to_string())))
261        };
262        let models = load_res.0;
263
264        // Load models
265        let mut vertices: Vec<Vertex> = Vec::new();
266        let mut indices: Vec<u32> = Vec::new();
267        let mut bounding_box = MeshBbox {
268            min: Vec3::new(f32::MAX, f32::MAX, f32::MAX),
269            max: Vec3::new(f32::MIN, f32::MIN, f32::MIN)
270        };
271        for m in models.iter() {
272            let mesh = &m.mesh;
273            if mesh.positions.len() % 3 != 0 {
274                return Err(MeshLoaderError::Io(std::io::Error::other(
275                    "Mesh positions are not divisible by 3."
276                )));
277            }
278
279            // Allocate sizes
280            vertices.reserve(mesh.positions.len() / 3);
281
282            // Create vertices
283            for vtx in 0..mesh.positions.len() / 3 {
284                let x = mesh.positions[3 * vtx];
285                let y = mesh.positions[3 * vtx + 1];
286                let z = mesh.positions[3 * vtx + 2];
287
288                // Normals
289                let mut nx = 0.0;
290                let mut ny = 0.0;
291                let mut nz = 0.0;
292                if mesh.normals.len() >= 3 * vtx + 2 {
293                    nx = mesh.normals[3 * vtx];
294                    ny = mesh.normals[3 * vtx + 1];
295                    nz = mesh.normals[3 * vtx + 2];
296                }
297
298                // UVs
299                let mut u = 0.0;
300                let mut v = 0.0;
301                if mesh.texcoords.len() > 2 * vtx {
302                    u = mesh.texcoords[2 * vtx];
303                    v = mesh.texcoords[2 * vtx + 1];
304                }
305
306                // Vertex
307                vertices.push(Vertex {
308                    position: [x, y, z],
309                    normal: [nx, ny, nz],
310                    uv: [u, v],
311                    tangent: [0.0, 0.0, 0.0, 0.0]
312                });
313
314                // Update bounding box
315                bounding_box.min.x = bounding_box.min.x.min(x);
316                bounding_box.min.y = bounding_box.min.y.min(y);
317                bounding_box.min.z = bounding_box.min.z.min(z);
318                bounding_box.max.x = bounding_box.max.x.max(x);
319                bounding_box.max.y = bounding_box.max.y.max(y);
320                bounding_box.max.z = bounding_box.max.z.max(z);
321            }
322
323            // Push indices
324            indices.extend_from_slice(&mesh.indices);
325        }
326        Ok(Mesh {
327            label,
328            vertices,
329            indices,
330            bbox: bounding_box,
331            use_ssbo: settings.use_ssbo
332        })
333    }
334
335    fn extensions(&self) -> &[&str] {
336        &["obj", "fbx"]
337    }
338}