use super::Vertex; use crate::{ pipeline::{ AsVertexBufferDescriptor, PrimitiveTopology, RenderPipelines, VertexBufferDescriptor, VertexBufferDescriptors, VertexFormat, }, renderer::{BufferInfo, BufferUsage, RenderResourceContext, RenderResourceId}, }; use bevy_app::prelude::{EventReader, Events}; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_core::AsBytes; use bevy_ecs::{Local, Query, Res, ResMut}; use bevy_math::*; use bevy_utils::HashSet; use std::borrow::Cow; use thiserror::Error; pub const VERTEX_BUFFER_ASSET_INDEX: usize = 0; pub const INDEX_BUFFER_ASSET_INDEX: usize = 1; #[derive(Clone, Debug)] pub enum VertexAttributeValues { Float(Vec), Float2(Vec<[f32; 2]>), Float3(Vec<[f32; 3]>), Float4(Vec<[f32; 4]>), } impl VertexAttributeValues { pub fn len(&self) -> usize { match *self { VertexAttributeValues::Float(ref values) => values.len(), VertexAttributeValues::Float2(ref values) => values.len(), VertexAttributeValues::Float3(ref values) => values.len(), VertexAttributeValues::Float4(ref values) => values.len(), } } pub fn is_empty(&self) -> bool { self.len() == 0 } // TODO: add vertex format as parameter here and perform type conversions pub fn get_bytes(&self) -> &[u8] { match self { VertexAttributeValues::Float(values) => values.as_slice().as_bytes(), VertexAttributeValues::Float2(values) => values.as_slice().as_bytes(), VertexAttributeValues::Float3(values) => values.as_slice().as_bytes(), VertexAttributeValues::Float4(values) => values.as_slice().as_bytes(), } } } impl From<&VertexAttributeValues> for VertexFormat { fn from(values: &VertexAttributeValues) -> Self { match values { VertexAttributeValues::Float(_) => VertexFormat::Float, VertexAttributeValues::Float2(_) => VertexFormat::Float2, VertexAttributeValues::Float3(_) => VertexFormat::Float3, VertexAttributeValues::Float4(_) => VertexFormat::Float4, } } } #[derive(Debug)] pub struct VertexAttribute { pub name: Cow<'static, str>, pub values: VertexAttributeValues, } impl VertexAttribute { pub const NORMAL: &'static str = "Vertex_Normal"; pub const POSITION: &'static str = "Vertex_Position"; pub const UV: &'static str = "Vertex_Uv"; pub fn position(positions: Vec<[f32; 3]>) -> Self { VertexAttribute { name: Self::POSITION.into(), values: VertexAttributeValues::Float3(positions), } } pub fn normal(normals: Vec<[f32; 3]>) -> Self { VertexAttribute { name: Self::NORMAL.into(), values: VertexAttributeValues::Float3(normals), } } pub fn uv(uvs: Vec<[f32; 2]>) -> Self { VertexAttribute { name: Self::UV.into(), values: VertexAttributeValues::Float2(uvs), } } } #[derive(Error, Debug)] pub enum MeshToVertexBufferError { #[error("VertexBufferDescriptor requires a VertexBufferAttribute this Mesh does not contain.")] MissingVertexAttribute { attribute_name: Cow<'static, str> }, #[error("Mesh VertexAttribute VertexFormat is incompatible with VertexBufferDescriptor VertexAttribute VertexFormat.")] IncompatibleVertexAttributeFormat { attribute_name: Cow<'static, str>, descriptor_format: VertexFormat, mesh_format: VertexFormat, }, } #[derive(Debug)] pub enum Indices { U16(Vec), U32(Vec), } #[derive(Debug)] pub struct Mesh { pub primitive_topology: PrimitiveTopology, pub attributes: Vec, pub indices: Option, } impl Mesh { pub fn new(primitive_topology: PrimitiveTopology) -> Self { Mesh { primitive_topology, attributes: Vec::new(), indices: None, } } pub fn get_vertex_buffer_bytes( &self, vertex_buffer_descriptor: &VertexBufferDescriptor, fill_missing_attributes: bool, ) -> Result, MeshToVertexBufferError> { let length = self.attributes.first().map(|a| a.values.len()).unwrap_or(0); let mut bytes = vec![0; vertex_buffer_descriptor.stride as usize * length]; for vertex_attribute in vertex_buffer_descriptor.attributes.iter() { match self .attributes .iter() .find(|a| vertex_attribute.name == a.name) { Some(mesh_attribute) => { let attribute_bytes = mesh_attribute.values.get_bytes(); let attribute_size = vertex_attribute.format.get_size() as usize; for (i, vertex_slice) in attribute_bytes.chunks(attribute_size).enumerate() { let vertex_offset = vertex_buffer_descriptor.stride as usize * i; let attribute_offset = vertex_offset + vertex_attribute.offset as usize; bytes[attribute_offset..attribute_offset + attribute_size] .copy_from_slice(vertex_slice); } } None => { if !fill_missing_attributes { return Err(MeshToVertexBufferError::MissingVertexAttribute { attribute_name: vertex_attribute.name.clone(), }); } } } } Ok(bytes) } pub fn get_index_buffer_bytes(&self) -> Option> { self.indices.as_ref().map(|indices| match &indices { Indices::U16(indices) => indices.as_slice().as_bytes().to_vec(), Indices::U32(indices) => indices.as_slice().as_bytes().to_vec(), }) } } /// Generation for some primitive shape meshes. pub mod shape { use super::{Indices, Mesh, VertexAttribute}; use crate::pipeline::PrimitiveTopology; use bevy_math::*; use hexasphere::Hexasphere; /// A cube. pub struct Cube { /// Half the side length of the cube. pub size: f32, } impl Default for Cube { fn default() -> Self { Cube { size: 1.0 } } } impl From for Mesh { fn from(cube: Cube) -> Self { let size = cube.size; let vertices = &[ // top (0., 0., size) ([-size, -size, size], [0., 0., size], [0., 0.]), ([size, -size, size], [0., 0., size], [size, 0.]), ([size, size, size], [0., 0., size], [size, size]), ([-size, size, size], [0., 0., size], [0., size]), // bottom (0., 0., -size) ([-size, size, -size], [0., 0., -size], [size, 0.]), ([size, size, -size], [0., 0., -size], [0., 0.]), ([size, -size, -size], [0., 0., -size], [0., size]), ([-size, -size, -size], [0., 0., -size], [size, size]), // right (size, 0., 0.) ([size, -size, -size], [size, 0., 0.], [0., 0.]), ([size, size, -size], [size, 0., 0.], [size, 0.]), ([size, size, size], [size, 0., 0.], [size, size]), ([size, -size, size], [size, 0., 0.], [0., size]), // left (-size, 0., 0.) ([-size, -size, size], [-size, 0., 0.], [size, 0.]), ([-size, size, size], [-size, 0., 0.], [0., 0.]), ([-size, size, -size], [-size, 0., 0.], [0., size]), ([-size, -size, -size], [-size, 0., 0.], [size, size]), // front (0., size, 0.) ([size, size, -size], [0., size, 0.], [size, 0.]), ([-size, size, -size], [0., size, 0.], [0., 0.]), ([-size, size, size], [0., size, 0.], [0., size]), ([size, size, size], [0., size, 0.], [size, size]), // back (0., -size, 0.) ([size, -size, size], [0., -size, 0.], [0., 0.]), ([-size, -size, size], [0., -size, 0.], [size, 0.]), ([-size, -size, -size], [0., -size, 0.], [size, size]), ([size, -size, -size], [0., -size, 0.], [0., size]), ]; let mut positions = Vec::new(); let mut normals = Vec::new(); let mut uvs = Vec::new(); for (position, normal, uv) in vertices.iter() { positions.push(*position); normals.push(*normal); uvs.push(*uv); } let indices = Indices::U32(vec![ 0, 1, 2, 2, 3, 0, // top 4, 5, 6, 6, 7, 4, // bottom 8, 9, 10, 10, 11, 8, // right 12, 13, 14, 14, 15, 12, // left 16, 17, 18, 18, 19, 16, // front 20, 21, 22, 22, 23, 20, // back ]); Mesh { primitive_topology: PrimitiveTopology::TriangleList, attributes: vec![ VertexAttribute::position(positions), VertexAttribute::normal(normals), VertexAttribute::uv(uvs), ], indices: Some(indices), } } } /// A rectangle on the XY plane. pub struct Quad { /// Full width and height of the rectangle. pub size: Vec2, /// Flips the texture coords of the resulting vertices. pub flip: bool, } impl Quad { pub fn new(size: Vec2) -> Self { Self { size, flip: false } } pub fn flipped(size: Vec2) -> Self { Self { size, flip: true } } } impl From for Mesh { fn from(quad: Quad) -> Self { let extent_x = quad.size.x() / 2.0; let extent_y = quad.size.y() / 2.0; let north_west = vec2(-extent_x, extent_y); let north_east = vec2(extent_x, extent_y); let south_west = vec2(-extent_x, -extent_y); let south_east = vec2(extent_x, -extent_y); let vertices = if quad.flip { [ ( [south_east.x(), south_east.y(), 0.0], [0.0, 0.0, 1.0], [1.0, 1.0], ), ( [north_east.x(), north_east.y(), 0.0], [0.0, 0.0, 1.0], [1.0, 0.0], ), ( [north_west.x(), north_west.y(), 0.0], [0.0, 0.0, 1.0], [0.0, 0.0], ), ( [south_west.x(), south_west.y(), 0.0], [0.0, 0.0, 1.0], [0.0, 1.0], ), ] } else { [ ( [south_west.x(), south_west.y(), 0.0], [0.0, 0.0, 1.0], [0.0, 1.0], ), ( [north_west.x(), north_west.y(), 0.0], [0.0, 0.0, 1.0], [0.0, 0.0], ), ( [north_east.x(), north_east.y(), 0.0], [0.0, 0.0, 1.0], [1.0, 0.0], ), ( [south_east.x(), south_east.y(), 0.0], [0.0, 0.0, 1.0], [1.0, 1.0], ), ] }; let indices = Indices::U32(vec![0, 2, 1, 0, 3, 2]); let mut positions = Vec::new(); let mut normals = Vec::new(); let mut uvs = Vec::new(); for (position, normal, uv) in vertices.iter() { positions.push(*position); normals.push(*normal); uvs.push(*uv); } Mesh { primitive_topology: PrimitiveTopology::TriangleList, attributes: vec![ VertexAttribute::position(positions), VertexAttribute::normal(normals), VertexAttribute::uv(uvs), ], indices: Some(indices), } } } /// A square on the XZ plane. pub struct Plane { /// The total side length of the square. pub size: f32, } impl From for Mesh { fn from(plane: Plane) -> Self { let extent = plane.size / 2.0; let vertices = [ ([extent, 0.0, -extent], [0.0, 1.0, 0.0], [1.0, 1.0]), ([extent, 0.0, extent], [0.0, 1.0, 0.0], [1.0, 0.0]), ([-extent, 0.0, extent], [0.0, 1.0, 0.0], [0.0, 0.0]), ([-extent, 0.0, -extent], [0.0, 1.0, 0.0], [0.0, 1.0]), ]; let indices = Indices::U32(vec![0, 2, 1, 0, 3, 2]); let mut positions = Vec::new(); let mut normals = Vec::new(); let mut uvs = Vec::new(); for (position, normal, uv) in vertices.iter() { positions.push(*position); normals.push(*normal); uvs.push(*uv); } Mesh { primitive_topology: PrimitiveTopology::TriangleList, attributes: vec![ VertexAttribute::position(positions), VertexAttribute::normal(normals), VertexAttribute::uv(uvs), ], indices: Some(indices), } } } /// A sphere made from a subdivided Icosahedron. pub struct Icosphere { /// The radius of the sphere. pub radius: f32, /// The number of subdivisions applied. pub subdivisions: usize, } impl Default for Icosphere { fn default() -> Self { Self { radius: 1.0, subdivisions: 5, } } } impl From for Mesh { fn from(sphere: Icosphere) -> Self { if sphere.subdivisions >= 80 { let temp_sphere = Hexasphere::new(sphere.subdivisions, |_| ()); panic!( "Cannot create an icosphere of {} subdivisions due to there being too many vertices being generated: {} (Limited to 65535 vertices or 79 subdivisions)", sphere.subdivisions, temp_sphere.raw_points().len() ); } let hexasphere = Hexasphere::new(sphere.subdivisions, |point| { let inclination = point.z().acos(); let azumith = point.y().atan2(point.x()); let norm_inclination = 1.0 - (inclination / std::f32::consts::PI); let norm_azumith = (azumith / std::f32::consts::PI) * 0.5; [norm_inclination, norm_azumith] }); let raw_points = hexasphere.raw_points(); let points = raw_points .iter() .map(|&p| (p * sphere.radius).into()) .collect::>(); let normals = raw_points .iter() .copied() .map(Into::into) .collect::>(); let uvs = hexasphere.raw_data().to_owned(); let mut indices = Vec::with_capacity(hexasphere.indices_per_main_triangle() * 20); for i in 0..20 { hexasphere.get_indices(i, &mut indices); } let indices = Indices::U32(indices); Mesh { primitive_topology: PrimitiveTopology::TriangleList, attributes: vec![ VertexAttribute::position(points), VertexAttribute::normal(normals), VertexAttribute::uv(uvs), ], indices: Some(indices), } } } } fn remove_current_mesh_resources( render_resource_context: &dyn RenderResourceContext, handle: Handle, ) { if let Some(RenderResourceId::Buffer(buffer)) = render_resource_context.get_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX) { render_resource_context.remove_buffer(buffer); render_resource_context.remove_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX); } if let Some(RenderResourceId::Buffer(buffer)) = render_resource_context.get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX) { render_resource_context.remove_buffer(buffer); render_resource_context.remove_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX); } } #[derive(Default)] pub struct MeshResourceProviderState { mesh_event_reader: EventReader>, vertex_buffer_descriptor: Option<&'static VertexBufferDescriptor>, } pub fn mesh_resource_provider_system( mut state: Local, render_resource_context: Res>, meshes: Res>, mut vertex_buffer_descriptors: ResMut, mesh_events: Res>>, mut query: Query<(&Handle, &mut RenderPipelines)>, ) { let vertex_buffer_descriptor = match state.vertex_buffer_descriptor { Some(value) => value, None => { // TODO: allow pipelines to specialize on vertex_buffer_descriptor and index_format let vertex_buffer_descriptor = Vertex::as_vertex_buffer_descriptor(); vertex_buffer_descriptors.set(vertex_buffer_descriptor.clone()); state.vertex_buffer_descriptor = Some(vertex_buffer_descriptor); vertex_buffer_descriptor } }; let mut changed_meshes = HashSet::>::default(); let render_resource_context = &**render_resource_context; for event in state.mesh_event_reader.iter(&mesh_events) { match event { AssetEvent::Created { handle } => { changed_meshes.insert(*handle); } AssetEvent::Modified { handle } => { changed_meshes.insert(*handle); remove_current_mesh_resources(render_resource_context, *handle); } AssetEvent::Removed { handle } => { remove_current_mesh_resources(render_resource_context, *handle); // if mesh was modified and removed in the same update, ignore the modification // events are ordered so future modification events are ok changed_meshes.remove(handle); } } } for changed_mesh_handle in changed_meshes.iter() { if let Some(mesh) = meshes.get(changed_mesh_handle) { let vertex_bytes = mesh .get_vertex_buffer_bytes(&vertex_buffer_descriptor, true) .unwrap(); // TODO: use a staging buffer here let vertex_buffer = render_resource_context.create_buffer_with_data( BufferInfo { buffer_usage: BufferUsage::VERTEX, ..Default::default() }, &vertex_bytes, ); let index_bytes = mesh.get_index_buffer_bytes().unwrap(); let index_buffer = render_resource_context.create_buffer_with_data( BufferInfo { buffer_usage: BufferUsage::INDEX, ..Default::default() }, &index_bytes, ); render_resource_context.set_asset_resource( *changed_mesh_handle, RenderResourceId::Buffer(vertex_buffer), VERTEX_BUFFER_ASSET_INDEX, ); render_resource_context.set_asset_resource( *changed_mesh_handle, RenderResourceId::Buffer(index_buffer), INDEX_BUFFER_ASSET_INDEX, ); } } // TODO: remove this once batches are pipeline specific and deprecate assigned_meshes draw target for (handle, mut render_pipelines) in &mut query.iter() { if let Some(mesh) = meshes.get(&handle) { for render_pipeline in render_pipelines.pipelines.iter_mut() { render_pipeline.specialization.primitive_topology = mesh.primitive_topology; } } if let Some(RenderResourceId::Buffer(vertex_buffer)) = render_resource_context.get_asset_resource(*handle, VERTEX_BUFFER_ASSET_INDEX) { render_pipelines.bindings.set_vertex_buffer( "Vertex", vertex_buffer, render_resource_context .get_asset_resource(*handle, INDEX_BUFFER_ASSET_INDEX) .and_then(|r| { if let RenderResourceId::Buffer(buffer) = r { Some(buffer) } else { None } }), ); } } } #[cfg(test)] mod tests { use super::{AsVertexBufferDescriptor, Mesh, VertexAttribute}; use crate::{mesh::Vertex, pipeline::PrimitiveTopology}; use bevy_core::AsBytes; #[test] fn test_get_vertex_bytes() { let vertices = &[ ([0., 0., 0.], [1., 1., 1.], [2., 2.]), ([3., 3., 3.], [4., 4., 4.], [5., 5.]), ([6., 6., 6.], [7., 7., 7.], [8., 8.]), ]; let mut positions = Vec::new(); let mut normals = Vec::new(); let mut uvs = Vec::new(); for (position, normal, uv) in vertices.iter() { positions.push(*position); normals.push(*normal); uvs.push(*uv); } let mesh = Mesh { primitive_topology: PrimitiveTopology::TriangleStrip, attributes: vec![ VertexAttribute::position(positions), VertexAttribute::normal(normals), VertexAttribute::uv(uvs), ], indices: None, }; let expected_vertices = &[ Vertex { position: [0., 0., 0.], normal: [1., 1., 1.], uv: [2., 2.], }, Vertex { position: [3., 3., 3.], normal: [4., 4., 4.], uv: [5., 5.], }, Vertex { position: [6., 6., 6.], normal: [7., 7., 7.], uv: [8., 8.], }, ]; let descriptor = Vertex::as_vertex_buffer_descriptor(); assert_eq!( mesh.get_vertex_buffer_bytes(descriptor, true).unwrap(), expected_vertices.as_bytes(), "buffer bytes are equal" ); } }