Path: blob/main/crates/bevy_mesh/src/primitives/extrusion.rs
6596 views
use bevy_math::{1primitives::{Annulus, Capsule2d, Circle, Ellipse, Extrusion, Primitive2d},2Vec2, Vec3,3};45use super::{MeshBuilder, Meshable};6use crate::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};78/// A type representing a segment of the perimeter of an extrudable mesh.9pub enum PerimeterSegment {10/// This segment of the perimeter will be shaded smooth.11///12/// This has the effect of rendering the segment's faces with softened edges, so it is appropriate for curved shapes.13///14/// The normals for the vertices that are part of this segment will be calculated based on the positions of their neighbors.15/// Each normal is interpolated between the normals of the two line segments connecting it with its neighbors.16/// Closer vertices have a stronger effect on the normal than more distant ones.17///18/// Since the vertices corresponding to the first and last indices do not have two neighboring vertices, their normals must be provided manually.19Smooth {20/// The normal of the first vertex.21first_normal: Vec2,22/// The normal of the last vertex.23last_normal: Vec2,24/// A list of indices representing this segment of the perimeter of the mesh.25///26/// The indices must be ordered such that the *outside* of the mesh is to the right27/// when walking along the vertices of the mesh in the order provided by the indices.28///29/// For geometry to be rendered, you must provide at least two indices.30indices: Vec<u32>,31},32/// This segment of the perimeter will be shaded flat.33///34/// This has the effect of rendering the segment's faces with hard edges.35Flat {36/// A list of indices representing this segment of the perimeter of the mesh.37///38/// The indices must be ordered such that the *outside* of the mesh is to the right39/// when walking along the vertices of the mesh in the order provided by indices.40///41/// For geometry to be rendered, you must provide at least two indices.42indices: Vec<u32>,43},44}4546impl PerimeterSegment {47/// Returns the amount of vertices each 'layer' of the extrusion should include for this perimeter segment.48///49/// A layer is the set of vertices sharing a common Z value or depth.50fn vertices_per_layer(&self) -> u32 {51match self {52PerimeterSegment::Smooth { indices, .. } => indices.len() as u32,53PerimeterSegment::Flat { indices } => 2 * (indices.len() as u32 - 1),54}55}5657/// Returns the amount of indices each 'segment' of the extrusion should include for this perimeter segment.58///59/// A segment is the set of faces on the mantel of the extrusion between two layers of vertices.60fn indices_per_segment(&self) -> usize {61match self {62PerimeterSegment::Smooth { indices, .. } | PerimeterSegment::Flat { indices } => {636 * (indices.len() - 1)64}65}66}67}6869/// A trait required for implementing `Meshable` for `Extrusion<T>`.70///71/// ## Warning72///73/// By implementing this trait you guarantee that the `primitive_topology` of the mesh returned by74/// this builder is [`PrimitiveTopology::TriangleList`]75/// and that your mesh has a [`Mesh::ATTRIBUTE_POSITION`] attribute.76pub trait Extrudable: MeshBuilder {77/// A list of the indices each representing a part of the perimeter of the mesh.78fn perimeter(&self) -> Vec<PerimeterSegment>;79}8081impl<P> Meshable for Extrusion<P>82where83P: Primitive2d + Meshable,84P::Output: Extrudable,85{86type Output = ExtrusionBuilder<P>;8788fn mesh(&self) -> Self::Output {89ExtrusionBuilder {90base_builder: self.base_shape.mesh(),91half_depth: self.half_depth,92segments: 1,93}94}95}9697/// A builder used for creating a [`Mesh`] with an [`Extrusion`] shape.98pub struct ExtrusionBuilder<P>99where100P: Primitive2d + Meshable,101P::Output: Extrudable,102{103pub base_builder: P::Output,104pub half_depth: f32,105pub segments: usize,106}107108impl<P> ExtrusionBuilder<P>109where110P: Primitive2d + Meshable,111P::Output: Extrudable,112{113/// Create a new `ExtrusionBuilder<P>` from a given `base_shape` and the full `depth` of the extrusion.114pub fn new(base_shape: &P, depth: f32) -> Self {115Self {116base_builder: base_shape.mesh(),117half_depth: depth / 2.,118segments: 1,119}120}121122/// Sets the number of segments along the depth of the extrusion.123/// Must be greater than `0` for the geometry of the mantel to be generated.124pub fn segments(mut self, segments: usize) -> Self {125self.segments = segments;126self127}128}129130impl ExtrusionBuilder<Circle> {131/// Sets the number of vertices used for the circle mesh at each end of the extrusion.132pub fn resolution(mut self, resolution: u32) -> Self {133self.base_builder.resolution = resolution;134self135}136}137138impl ExtrusionBuilder<Ellipse> {139/// Sets the number of vertices used for the ellipse mesh at each end of the extrusion.140pub fn resolution(mut self, resolution: u32) -> Self {141self.base_builder.resolution = resolution;142self143}144}145146impl ExtrusionBuilder<Annulus> {147/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh at each end of the extrusion.148pub fn resolution(mut self, resolution: u32) -> Self {149self.base_builder.resolution = resolution;150self151}152}153154impl ExtrusionBuilder<Capsule2d> {155/// Sets the number of vertices used for each hemicircle at the ends of the extrusion.156pub fn resolution(mut self, resolution: u32) -> Self {157self.base_builder.resolution = resolution;158self159}160}161162impl<P> MeshBuilder for ExtrusionBuilder<P>163where164P: Primitive2d + Meshable,165P::Output: Extrudable,166{167fn build(&self) -> Mesh {168// Create and move the base mesh to the front169let mut front_face =170self.base_builder171.build()172.translated_by(Vec3::new(0., 0., self.half_depth));173174// Move the uvs of the front face to be between (0., 0.) and (0.5, 0.5)175if let Some(VertexAttributeValues::Float32x2(uvs)) =176front_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)177{178for uv in uvs {179*uv = uv.map(|coord| coord * 0.5);180}181}182183let back_face = {184let topology = front_face.primitive_topology();185// Flip the normals, etc. and move mesh to the back186let mut back_face = front_face.clone().scaled_by(Vec3::new(1., 1., -1.));187188// Move the uvs of the back face to be between (0.5, 0.) and (1., 0.5)189if let Some(VertexAttributeValues::Float32x2(uvs)) =190back_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)191{192for uv in uvs {193*uv = [uv[0] + 0.5, uv[1]];194}195}196197// By swapping the first and second indices of each triangle we invert the winding order thus making the mesh visible from the other side198if let Some(indices) = back_face.indices_mut() {199match topology {200PrimitiveTopology::TriangleList => match indices {201Indices::U16(indices) => {202indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));203}204Indices::U32(indices) => {205indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));206}207},208_ => {209panic!("Meshes used with Extrusions must have a primitive topology of `PrimitiveTopology::TriangleList`");210}211};212}213back_face214};215216// An extrusion of depth 0 does not need a mantel217if self.half_depth == 0. {218front_face.merge(&back_face).unwrap();219return front_face;220}221222let mantel = {223let Some(VertexAttributeValues::Float32x3(cap_verts)) =224front_face.attribute(Mesh::ATTRIBUTE_POSITION)225else {226panic!("The base mesh did not have vertex positions");227};228229debug_assert!(self.segments > 0);230231let layers = self.segments + 1;232let layer_depth_delta = self.half_depth * 2.0 / self.segments as f32;233234let perimeter = self.base_builder.perimeter();235let (vert_count, index_count) =236perimeter237.iter()238.fold((0, 0), |(verts, indices), perimeter| {239(240verts + layers * perimeter.vertices_per_layer() as usize,241indices + self.segments * perimeter.indices_per_segment(),242)243});244let mut positions = Vec::with_capacity(vert_count);245let mut normals = Vec::with_capacity(vert_count);246let mut indices = Vec::with_capacity(index_count);247let mut uvs = Vec::with_capacity(vert_count);248249// Compute the amount of horizontal space allocated to each segment of the perimeter.250let uv_segment_delta = 1. / perimeter.len() as f32;251for (i, segment) in perimeter.into_iter().enumerate() {252// The start of the x range of the area of the current perimeter-segment.253let uv_start = i as f32 * uv_segment_delta;254255match segment {256PerimeterSegment::Flat {257indices: segment_indices,258} => {259let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;260for i in 0..(segment_indices.len() - 1) {261let uv_x = uv_start + uv_delta * i as f32;262// Get the positions for the current and the next index.263let a = cap_verts[segment_indices[i] as usize];264let b = cap_verts[segment_indices[i + 1] as usize];265266// Get the index of the next vertex added to the mantel.267let index = positions.len() as u32;268269// Push the positions of the two indices and their equivalent points on each layer.270for i in 0..layers {271let i = i as f32;272let z = a[2] - layer_depth_delta * i;273positions.push([a[0], a[1], z]);274positions.push([b[0], b[1], z]);275276// UVs for the mantel are between (0, 0.5) and (1, 1).277let uv_y = 0.5 + 0.5 * i / self.segments as f32;278uvs.push([uv_x, uv_y]);279uvs.push([uv_x + uv_delta, uv_y]);280}281282// The normal is calculated to be the normal of the line segment connecting a and b.283let n = Vec3::from_array([b[1] - a[1], a[0] - b[0], 0.])284.normalize_or_zero()285.to_array();286normals.extend_from_slice(&vec![n; 2 * layers]);287288// Add the indices for the vertices created above to the mesh.289for i in 0..self.segments as u32 {290let base_index = index + 2 * i;291indices.extend_from_slice(&[292base_index,293base_index + 2,294base_index + 1,295base_index + 1,296base_index + 2,297base_index + 3,298]);299}300}301}302PerimeterSegment::Smooth {303first_normal,304last_normal,305indices: segment_indices,306} => {307let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;308309// Since the indices for this segment will be added after its vertices have been added,310// we need to store the index of the first vertex that is part of this segment.311let base_index = positions.len() as u32;312313// If there is a first vertex, we need to add it and its counterparts on each layer.314// The normal is provided by `segment.first_normal`.315if let Some(i) = segment_indices.first() {316let p = cap_verts[*i as usize];317for i in 0..layers {318let i = i as f32;319let z = p[2] - layer_depth_delta * i;320positions.push([p[0], p[1], z]);321322let uv_y = 0.5 + 0.5 * i / self.segments as f32;323uvs.push([uv_start, uv_y]);324}325normals.extend_from_slice(&vec![326first_normal.extend(0.).to_array();327layers328]);329}330331// For all points inbetween the first and last vertices, we can automatically compute the normals.332for i in 1..(segment_indices.len() - 1) {333let uv_x = uv_start + uv_delta * i as f32;334335// Get the positions for the last, current and the next index.336let a = cap_verts[segment_indices[i - 1] as usize];337let b = cap_verts[segment_indices[i] as usize];338let c = cap_verts[segment_indices[i + 1] as usize];339340// Add the current vertex and its counterparts on each layer.341for i in 0..layers {342let i = i as f32;343let z = b[2] - layer_depth_delta * i;344positions.push([b[0], b[1], z]);345346let uv_y = 0.5 + 0.5 * i / self.segments as f32;347uvs.push([uv_x, uv_y]);348}349350// The normal for the current vertices can be calculated based on the two neighboring vertices.351// The normal is interpolated between the normals of the two line segments connecting the current vertex with its neighbors.352// Closer vertices have a stronger effect on the normal than more distant ones.353let n = {354let ab = Vec2::from_slice(&b) - Vec2::from_slice(&a);355let bc = Vec2::from_slice(&c) - Vec2::from_slice(&b);356let n = ab.normalize_or_zero() + bc.normalize_or_zero();357Vec2::new(n.y, -n.x)358.normalize_or_zero()359.extend(0.)360.to_array()361};362normals.extend_from_slice(&vec![n; layers]);363}364365// If there is a last vertex, we need to add it and its counterparts on each layer.366// The normal is provided by `segment.last_normal`.367if let Some(i) = segment_indices.last() {368let p = cap_verts[*i as usize];369for i in 0..layers {370let i = i as f32;371let z = p[2] - layer_depth_delta * i;372positions.push([p[0], p[1], z]);373374let uv_y = 0.5 + 0.5 * i / self.segments as f32;375uvs.push([uv_start + uv_segment_delta, uv_y]);376}377normals.extend_from_slice(&vec![378last_normal.extend(0.).to_array();379layers380]);381}382383let columns = segment_indices.len() as u32;384let segments = self.segments as u32;385let layers = segments + 1;386for s in 0..segments {387for column in 0..(columns - 1) {388let index = base_index + s + column * layers;389indices.extend_from_slice(&[390index,391index + 1,392index + layers,393index + layers,394index + 1,395index + layers + 1,396]);397}398}399}400}401}402403Mesh::new(PrimitiveTopology::TriangleList, front_face.asset_usage)404.with_inserted_indices(Indices::U32(indices))405.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)406.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)407.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)408};409410front_face.merge(&back_face).unwrap();411front_face.merge(&mantel).unwrap();412front_face413}414}415416impl<P> From<Extrusion<P>> for Mesh417where418P: Primitive2d + Meshable,419P::Output: Extrudable,420{421fn from(value: Extrusion<P>) -> Self {422value.mesh().build()423}424}425426427