Path: blob/main/crates/bevy_mesh/src/primitives/extrusion.rs
9423 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` refer to the indices of the vertices generated by the `MeshBuilder` of the underlying 2D primitive.27/// For example, a triangle has 3 vertices with indices 0, 1 and 2.28///29/// The indices must be ordered such that the *outside* of the mesh is to the right30/// when walking along the vertices of the mesh in the order provided by the indices.31///32/// For geometry to be rendered, you must provide at least two indices.33indices: Vec<u32>,34},35/// This segment of the perimeter will be shaded flat.36///37/// This has the effect of rendering the segment's faces with hard edges.38Flat {39/// A list of indices representing this segment of the perimeter of the mesh.40///41/// The `indices` refer to the indices of the vertices generated by the `MeshBuilder` of the underlying 2D primitive.42/// For example, a triangle has 3 vertices with indices 0, 1 and 2.43///44/// The indices must be ordered such that the *outside* of the mesh is to the right45/// when walking along the vertices of the mesh in the order provided by indices.46///47/// For geometry to be rendered, you must provide at least two indices.48indices: Vec<u32>,49},50}5152impl PerimeterSegment {53/// Returns the amount of vertices each 'layer' of the extrusion should include for this perimeter segment.54///55/// A layer is the set of vertices sharing a common Z value or depth.56fn vertices_per_layer(&self) -> u32 {57match self {58PerimeterSegment::Smooth { indices, .. } => indices.len() as u32,59PerimeterSegment::Flat { indices } => 2 * (indices.len() as u32 - 1),60}61}6263/// Returns the amount of indices each 'segment' of the extrusion should include for this perimeter segment.64///65/// A segment is the set of faces on the mantel of the extrusion between two layers of vertices.66fn indices_per_segment(&self) -> usize {67match self {68PerimeterSegment::Smooth { indices, .. } | PerimeterSegment::Flat { indices } => {696 * (indices.len() - 1)70}71}72}73}7475/// A trait required for implementing `Meshable` for `Extrusion<T>`.76///77/// ## Warning78///79/// By implementing this trait you guarantee that the `primitive_topology` of the mesh returned by80/// this builder is [`PrimitiveTopology::TriangleList`]81/// and that your mesh has a [`Mesh::ATTRIBUTE_POSITION`] attribute.82pub trait Extrudable: MeshBuilder {83/// A list of the indices each representing a part of the perimeter of the mesh.84fn perimeter(&self) -> Vec<PerimeterSegment>;85}8687impl<P> Meshable for Extrusion<P>88where89P: Primitive2d + Meshable,90P::Output: Extrudable,91{92type Output = ExtrusionBuilder<P>;9394fn mesh(&self) -> Self::Output {95ExtrusionBuilder {96base_builder: self.base_shape.mesh(),97half_depth: self.half_depth,98segments: 1,99}100}101}102103/// A builder used for creating a [`Mesh`] with an [`Extrusion`] shape.104pub struct ExtrusionBuilder<P>105where106P: Primitive2d + Meshable,107P::Output: Extrudable,108{109pub base_builder: P::Output,110pub half_depth: f32,111pub segments: usize,112}113114impl<P> ExtrusionBuilder<P>115where116P: Primitive2d + Meshable,117P::Output: Extrudable,118{119/// Create a new `ExtrusionBuilder<P>` from a given `base_shape` and the full `depth` of the extrusion.120pub fn new(base_shape: &P, depth: f32) -> Self {121Self {122base_builder: base_shape.mesh(),123half_depth: depth / 2.,124segments: 1,125}126}127128/// Sets the number of segments along the depth of the extrusion.129/// Must be greater than `0` for the geometry of the mantel to be generated.130pub fn segments(mut self, segments: usize) -> Self {131self.segments = segments;132self133}134135/// Apply a function to the inner builder136pub fn with_inner(mut self, func: impl Fn(P::Output) -> P::Output) -> Self {137self.base_builder = func(self.base_builder);138self139}140}141142impl ExtrusionBuilder<Circle> {143/// Sets the number of vertices used for the circle mesh at each end of the extrusion.144pub fn resolution(mut self, resolution: u32) -> Self {145self.base_builder.resolution = resolution;146self147}148}149150impl ExtrusionBuilder<Ellipse> {151/// Sets the number of vertices used for the ellipse mesh at each end of the extrusion.152pub fn resolution(mut self, resolution: u32) -> Self {153self.base_builder.resolution = resolution;154self155}156}157158impl ExtrusionBuilder<Annulus> {159/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh at each end of the extrusion.160pub fn resolution(mut self, resolution: u32) -> Self {161self.base_builder.resolution = resolution;162self163}164}165166impl ExtrusionBuilder<Capsule2d> {167/// Sets the number of vertices used for each hemicircle at the ends of the extrusion.168pub fn resolution(mut self, resolution: u32) -> Self {169self.base_builder.resolution = resolution;170self171}172}173174impl<P> MeshBuilder for ExtrusionBuilder<P>175where176P: Primitive2d + Meshable,177P::Output: Extrudable,178{179fn build(&self) -> Mesh {180// Create and move the base mesh to the front181let mut front_face =182self.base_builder183.build()184.translated_by(Vec3::new(0., 0., self.half_depth));185186// Move the uvs of the front face to be between (0., 0.) and (0.5, 0.5)187if let Some(VertexAttributeValues::Float32x2(uvs)) =188front_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)189{190for uv in uvs {191*uv = uv.map(|coord| coord * 0.5);192}193}194195let back_face = {196let topology = front_face.primitive_topology();197// Flip the normals, etc. and move mesh to the back198let mut back_face = front_face.clone().scaled_by(Vec3::new(1., 1., -1.));199200// Move the uvs of the back face to be between (0.5, 0.) and (1., 0.5)201if let Some(VertexAttributeValues::Float32x2(uvs)) =202back_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)203{204for uv in uvs {205*uv = [uv[0] + 0.5, uv[1]];206}207}208209// By swapping the first and second indices of each triangle we invert the winding order thus making the mesh visible from the other side210if let Some(indices) = back_face.indices_mut() {211match topology {212PrimitiveTopology::TriangleList => match indices {213Indices::U16(indices) => {214indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));215}216Indices::U32(indices) => {217indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));218}219},220_ => {221panic!("Meshes used with Extrusions must have a primitive topology of `PrimitiveTopology::TriangleList`");222}223};224}225back_face226};227228// An extrusion of depth 0 does not need a mantel229if self.half_depth == 0. {230front_face.merge(&back_face).unwrap();231return front_face;232}233234let mantel = {235let Some(VertexAttributeValues::Float32x3(cap_verts)) =236front_face.attribute(Mesh::ATTRIBUTE_POSITION)237else {238panic!("The base mesh did not have vertex positions");239};240241debug_assert!(self.segments > 0);242243let layers = self.segments + 1;244let layer_depth_delta = self.half_depth * 2.0 / self.segments as f32;245246let perimeter = self.base_builder.perimeter();247let (vert_count, index_count) =248perimeter249.iter()250.fold((0, 0), |(verts, indices), perimeter| {251(252verts + layers * perimeter.vertices_per_layer() as usize,253indices + self.segments * perimeter.indices_per_segment(),254)255});256let mut positions = Vec::with_capacity(vert_count);257let mut normals = Vec::with_capacity(vert_count);258let mut indices = Vec::with_capacity(index_count);259let mut uvs = Vec::with_capacity(vert_count);260261// Compute the amount of horizontal space allocated to each segment of the perimeter.262let uv_segment_delta = 1. / perimeter.len() as f32;263for (i, segment) in perimeter.into_iter().enumerate() {264// The start of the x range of the area of the current perimeter-segment.265let uv_start = i as f32 * uv_segment_delta;266267match segment {268PerimeterSegment::Flat {269indices: segment_indices,270} => {271let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;272for i in 0..(segment_indices.len() - 1) {273let uv_x = uv_start + uv_delta * i as f32;274// Get the positions for the current and the next index.275let a = cap_verts[segment_indices[i] as usize];276let b = cap_verts[segment_indices[i + 1] as usize];277278// Get the index of the next vertex added to the mantel.279let index = positions.len() as u32;280281// Push the positions of the two indices and their equivalent points on each layer.282for i in 0..layers {283let i = i as f32;284let z = a[2] - layer_depth_delta * i;285positions.push([a[0], a[1], z]);286positions.push([b[0], b[1], z]);287288// UVs for the mantel are between (0, 0.5) and (1, 1).289let uv_y = 0.5 + 0.5 * i / self.segments as f32;290uvs.push([uv_x, uv_y]);291uvs.push([uv_x + uv_delta, uv_y]);292}293294// The normal is calculated to be the normal of the line segment connecting a and b.295let n = Vec3::from_array([b[1] - a[1], a[0] - b[0], 0.])296.normalize_or_zero()297.to_array();298normals.extend_from_slice(&vec![n; 2 * layers]);299300// Add the indices for the vertices created above to the mesh.301for i in 0..self.segments as u32 {302let base_index = index + 2 * i;303indices.extend_from_slice(&[304base_index,305base_index + 2,306base_index + 1,307base_index + 1,308base_index + 2,309base_index + 3,310]);311}312}313}314PerimeterSegment::Smooth {315first_normal,316last_normal,317indices: segment_indices,318} => {319let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;320321// Since the indices for this segment will be added after its vertices have been added,322// we need to store the index of the first vertex that is part of this segment.323let base_index = positions.len() as u32;324325// If there is a first vertex, we need to add it and its counterparts on each layer.326// The normal is provided by `segment.first_normal`.327if let Some(i) = segment_indices.first() {328let p = cap_verts[*i as usize];329for i in 0..layers {330let i = i as f32;331let z = p[2] - layer_depth_delta * i;332positions.push([p[0], p[1], z]);333334let uv_y = 0.5 + 0.5 * i / self.segments as f32;335uvs.push([uv_start, uv_y]);336}337normals.extend_from_slice(&vec![338first_normal.extend(0.).to_array();339layers340]);341}342343// For all points inbetween the first and last vertices, we can automatically compute the normals.344for i in 1..(segment_indices.len() - 1) {345let uv_x = uv_start + uv_delta * i as f32;346347// Get the positions for the last, current and the next index.348let a = cap_verts[segment_indices[i - 1] as usize];349let b = cap_verts[segment_indices[i] as usize];350let c = cap_verts[segment_indices[i + 1] as usize];351352// Add the current vertex and its counterparts on each layer.353for i in 0..layers {354let i = i as f32;355let z = b[2] - layer_depth_delta * i;356positions.push([b[0], b[1], z]);357358let uv_y = 0.5 + 0.5 * i / self.segments as f32;359uvs.push([uv_x, uv_y]);360}361362// The normal for the current vertices can be calculated based on the two neighboring vertices.363// The normal is interpolated between the normals of the two line segments connecting the current vertex with its neighbors.364// Closer vertices have a stronger effect on the normal than more distant ones.365let n = {366let ab = Vec2::from_slice(&b) - Vec2::from_slice(&a);367let bc = Vec2::from_slice(&c) - Vec2::from_slice(&b);368let n = ab.normalize_or_zero() + bc.normalize_or_zero();369Vec2::new(n.y, -n.x)370.normalize_or_zero()371.extend(0.)372.to_array()373};374normals.extend_from_slice(&vec![n; layers]);375}376377// If there is a last vertex, we need to add it and its counterparts on each layer.378// The normal is provided by `segment.last_normal`.379if let Some(i) = segment_indices.last() {380let p = cap_verts[*i as usize];381for i in 0..layers {382let i = i as f32;383let z = p[2] - layer_depth_delta * i;384positions.push([p[0], p[1], z]);385386let uv_y = 0.5 + 0.5 * i / self.segments as f32;387uvs.push([uv_start + uv_segment_delta, uv_y]);388}389normals.extend_from_slice(&vec![390last_normal.extend(0.).to_array();391layers392]);393}394395let columns = segment_indices.len() as u32;396let segments = self.segments as u32;397let layers = segments + 1;398for s in 0..segments {399for column in 0..(columns - 1) {400let index = base_index + s + column * layers;401indices.extend_from_slice(&[402index,403index + 1,404index + layers,405index + layers,406index + 1,407index + layers + 1,408]);409}410}411}412}413}414415Mesh::new(PrimitiveTopology::TriangleList, front_face.asset_usage)416.with_inserted_indices(Indices::U32(indices))417.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)418.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)419.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)420};421422front_face.merge(&back_face).unwrap();423front_face.merge(&mantel).unwrap();424front_face425}426}427428impl<P> From<Extrusion<P>> for Mesh429where430P: Primitive2d + Meshable,431P::Output: Extrudable,432{433fn from(value: Extrusion<P>) -> Self {434value.mesh().build()435}436}437438439