Path: blob/main/crates/bevy_mesh/src/primitives/dim3/capsule.rs
6598 views
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};1use bevy_asset::RenderAssetUsages;2use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3};3use bevy_reflect::prelude::*;45/// Manner in which UV coordinates are distributed vertically.6#[derive(Clone, Copy, Debug, Default, Reflect)]7#[reflect(Default, Debug, Clone)]8pub enum CapsuleUvProfile {9/// UV space is distributed by how much of the capsule consists of the hemispheres.10#[default]11Aspect,12/// Hemispheres get UV space according to the ratio of latitudes to rings.13Uniform,14/// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder15/// and lower third to the southern one.16Fixed,17}1819/// A builder used for creating a [`Mesh`] with a [`Capsule3d`] shape.20#[derive(Clone, Copy, Debug, Reflect)]21#[reflect(Default, Debug, Clone)]22pub struct Capsule3dMeshBuilder {23/// The [`Capsule3d`] shape.24pub capsule: Capsule3d,25/// The number of horizontal lines subdividing the cylindrical part of the capsule.26/// The default is `0`.27pub rings: u32,28/// The number of vertical lines subdividing the hemispheres of the capsule.29/// The default is `32`.30pub longitudes: u32,31/// The number of horizontal lines subdividing the hemispheres of the capsule.32/// The default is `16`.33pub latitudes: u32,34/// The manner in which UV coordinates are distributed vertically.35/// The default is [`CapsuleUvProfile::Aspect`].36pub uv_profile: CapsuleUvProfile,37}3839impl Default for Capsule3dMeshBuilder {40fn default() -> Self {41Self {42capsule: Capsule3d::default(),43rings: 0,44longitudes: 32,45latitudes: 16,46uv_profile: CapsuleUvProfile::default(),47}48}49}5051impl Capsule3dMeshBuilder {52/// Creates a new [`Capsule3dMeshBuilder`] from a given radius, height, longitudes, and latitudes.53///54/// Note that `height` is the distance between the centers of the hemispheres.55/// `radius` will be added to both ends to get the real height of the mesh.56#[inline]57pub fn new(radius: f32, height: f32, longitudes: u32, latitudes: u32) -> Self {58Self {59capsule: Capsule3d::new(radius, height),60longitudes,61latitudes,62..Default::default()63}64}6566/// Sets the number of horizontal lines subdividing the cylindrical part of the capsule.67#[inline]68pub const fn rings(mut self, rings: u32) -> Self {69self.rings = rings;70self71}7273/// Sets the number of vertical lines subdividing the hemispheres of the capsule.74#[inline]75pub const fn longitudes(mut self, longitudes: u32) -> Self {76self.longitudes = longitudes;77self78}7980/// Sets the number of horizontal lines subdividing the hemispheres of the capsule.81#[inline]82pub const fn latitudes(mut self, latitudes: u32) -> Self {83self.latitudes = latitudes;84self85}8687/// Sets the manner in which UV coordinates are distributed vertically.88#[inline]89pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self {90self.uv_profile = uv_profile;91self92}93}9495impl MeshBuilder for Capsule3dMeshBuilder {96fn build(&self) -> Mesh {97// code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db98let Capsule3dMeshBuilder {99capsule,100rings,101longitudes,102latitudes,103uv_profile,104} = *self;105let Capsule3d {106radius,107half_length,108} = capsule;109110let calc_middle = rings > 0;111let half_lats = latitudes / 2;112let half_latsn1 = half_lats - 1;113let half_latsn2 = half_lats - 2;114let ringsp1 = rings + 1;115let lonsp1 = longitudes + 1;116let summit = half_length + radius;117118// Vertex index offsets.119let vert_offset_north_hemi = longitudes;120let vert_offset_north_equator = vert_offset_north_hemi + lonsp1 * half_latsn1;121let vert_offset_cylinder = vert_offset_north_equator + lonsp1;122let vert_offset_south_equator = if calc_middle {123vert_offset_cylinder + lonsp1 * rings124} else {125vert_offset_cylinder126};127let vert_offset_south_hemi = vert_offset_south_equator + lonsp1;128let vert_offset_south_polar = vert_offset_south_hemi + lonsp1 * half_latsn2;129let vert_offset_south_cap = vert_offset_south_polar + lonsp1;130131// Initialize arrays.132let vert_len = (vert_offset_south_cap + longitudes) as usize;133134let mut vs: Vec<Vec3> = vec![Vec3::ZERO; vert_len];135let mut vts: Vec<Vec2> = vec![Vec2::ZERO; vert_len];136let mut vns: Vec<Vec3> = vec![Vec3::ZERO; vert_len];137138let to_theta = 2.0 * core::f32::consts::PI / longitudes as f32;139let to_phi = core::f32::consts::PI / latitudes as f32;140let to_tex_horizontal = 1.0 / longitudes as f32;141let to_tex_vertical = 1.0 / half_lats as f32;142143let vt_aspect_ratio = match uv_profile {144CapsuleUvProfile::Aspect => radius / (2.0 * half_length + radius + radius),145CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32,146CapsuleUvProfile::Fixed => 1.0 / 3.0,147};148let vt_aspect_north = 1.0 - vt_aspect_ratio;149let vt_aspect_south = vt_aspect_ratio;150151let mut theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];152let mut rho_theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];153let mut s_texture_cache: Vec<f32> = vec![0.0; lonsp1 as usize];154155for j in 0..longitudes as usize {156let jf = j as f32;157let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal);158let theta = jf * to_theta;159160theta_cartesian[j] = Vec2::from_angle(theta);161rho_theta_cartesian[j] = radius * theta_cartesian[j];162163// North.164vs[j] = Vec3::new(0.0, summit, 0.0);165vts[j] = Vec2::new(s_texture_polar, 1.0);166vns[j] = Vec3::Y;167168// South.169let idx = vert_offset_south_cap as usize + j;170vs[idx] = Vec3::new(0.0, -summit, 0.0);171vts[idx] = Vec2::new(s_texture_polar, 0.0);172vns[idx] = Vec3::new(0.0, -1.0, 0.0);173}174175// Equatorial vertices.176for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1 as usize) {177let s_texture = 1.0 - j as f32 * to_tex_horizontal;178*s_texture_cache_j = s_texture;179180// Wrap to first element upon reaching last.181let j_mod = j % longitudes as usize;182let tc = theta_cartesian[j_mod];183let rtc = rho_theta_cartesian[j_mod];184185// North equator.186let idxn = vert_offset_north_equator as usize + j;187vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y);188vts[idxn] = Vec2::new(s_texture, vt_aspect_north);189vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y);190191// South equator.192let idxs = vert_offset_south_equator as usize + j;193vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y);194vts[idxs] = Vec2::new(s_texture, vt_aspect_south);195vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y);196}197198// Hemisphere vertices.199for i in 0..half_latsn1 {200let ip1f = i as f32 + 1.0;201let phi = ip1f * to_phi;202203// For coordinates.204let (sin_phi_south, cos_phi_south) = ops::sin_cos(phi);205206// Symmetrical hemispheres mean cosine and sine only needs207// to be calculated once.208let cos_phi_north = sin_phi_south;209let sin_phi_north = -cos_phi_south;210211let rho_cos_phi_north = radius * cos_phi_north;212let rho_sin_phi_north = radius * sin_phi_north;213let z_offset_north = half_length - rho_sin_phi_north;214215let rho_cos_phi_south = radius * cos_phi_south;216let rho_sin_phi_south = radius * sin_phi_south;217let z_offset_sout = -half_length - rho_sin_phi_south;218219// For texture coordinates.220let t_tex_fac = ip1f * to_tex_vertical;221let cmpl_tex_fac = 1.0 - t_tex_fac;222let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac;223let t_tex_south = cmpl_tex_fac * vt_aspect_south;224225let i_lonsp1 = i * lonsp1;226let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;227let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1;228229for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {230let j_mod = j % longitudes as usize;231232let tc = theta_cartesian[j_mod];233234// North hemisphere.235let idxn = vert_curr_lat_north as usize + j;236vs[idxn] = Vec3::new(237rho_cos_phi_north * tc.x,238z_offset_north,239-rho_cos_phi_north * tc.y,240);241vts[idxn] = Vec2::new(*s_texture, t_tex_north);242vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y);243244// South hemisphere.245let idxs = vert_curr_lat_south as usize + j;246vs[idxs] = Vec3::new(247rho_cos_phi_south * tc.x,248z_offset_sout,249-rho_cos_phi_south * tc.y,250);251vts[idxs] = Vec2::new(*s_texture, t_tex_south);252vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y);253}254}255256// Cylinder vertices.257if calc_middle {258// Exclude both origin and destination edges259// (North and South equators) from the interpolation.260let to_fac = 1.0 / ringsp1 as f32;261let mut idx_cyl_lat = vert_offset_cylinder as usize;262263for h in 1..ringsp1 {264let fac = h as f32 * to_fac;265let cmpl_fac = 1.0 - fac;266let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south;267let z = half_length - 2.0 * half_length * fac;268269for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {270let j_mod = j % longitudes as usize;271let tc = theta_cartesian[j_mod];272let rtc = rho_theta_cartesian[j_mod];273274vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y);275vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture);276vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y);277278idx_cyl_lat += 1;279}280}281}282283// Triangle indices.284285// Stride is 3 for polar triangles;286// stride is 6 for two triangles forming a quad.287let lons3 = longitudes * 3;288let lons6 = longitudes * 6;289let hemi_lons = half_latsn1 * lons6;290291let tri_offset_north_hemi = lons3;292let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons;293let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6;294let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons;295296let fs_len = tri_offset_south_cap + lons3;297let mut tris: Vec<u32> = vec![0; fs_len as usize];298299// Polar caps.300let mut i = 0;301let mut k = 0;302let mut m = tri_offset_south_cap as usize;303while i < longitudes {304// North.305tris[k] = i;306tris[k + 1] = vert_offset_north_hemi + i;307tris[k + 2] = vert_offset_north_hemi + i + 1;308309// South.310tris[m] = vert_offset_south_cap + i;311tris[m + 1] = vert_offset_south_polar + i + 1;312tris[m + 2] = vert_offset_south_polar + i;313314i += 1;315k += 3;316m += 3;317}318319// Hemispheres.320321let mut i = 0;322let mut k = tri_offset_north_hemi as usize;323let mut m = tri_offset_south_hemi as usize;324325while i < half_latsn1 {326let i_lonsp1 = i * lonsp1;327328let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;329let vert_next_lat_north = vert_curr_lat_north + lonsp1;330331let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1;332let vert_next_lat_south = vert_curr_lat_south + lonsp1;333334let mut j = 0;335while j < longitudes {336// North.337let north00 = vert_curr_lat_north + j;338let north01 = vert_next_lat_north + j;339let north11 = vert_next_lat_north + j + 1;340let north10 = vert_curr_lat_north + j + 1;341342tris[k] = north00;343tris[k + 1] = north11;344tris[k + 2] = north10;345346tris[k + 3] = north00;347tris[k + 4] = north01;348tris[k + 5] = north11;349350// South.351let south00 = vert_curr_lat_south + j;352let south01 = vert_next_lat_south + j;353let south11 = vert_next_lat_south + j + 1;354let south10 = vert_curr_lat_south + j + 1;355356tris[m] = south00;357tris[m + 1] = south11;358tris[m + 2] = south10;359360tris[m + 3] = south00;361tris[m + 4] = south01;362tris[m + 5] = south11;363364j += 1;365k += 6;366m += 6;367}368369i += 1;370}371372// Cylinder.373let mut i = 0;374let mut k = tri_offset_cylinder as usize;375376while i < ringsp1 {377let vert_curr_lat = vert_offset_north_equator + i * lonsp1;378let vert_next_lat = vert_curr_lat + lonsp1;379380let mut j = 0;381while j < longitudes {382let cy00 = vert_curr_lat + j;383let cy01 = vert_next_lat + j;384let cy11 = vert_next_lat + j + 1;385let cy10 = vert_curr_lat + j + 1;386387tris[k] = cy00;388tris[k + 1] = cy11;389tris[k + 2] = cy10;390391tris[k + 3] = cy00;392tris[k + 4] = cy01;393tris[k + 5] = cy11;394395j += 1;396k += 6;397}398399i += 1;400}401402let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect();403let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect();404let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect();405406assert_eq!(vs.len(), vert_len);407assert_eq!(tris.len(), fs_len as usize);408409Mesh::new(410PrimitiveTopology::TriangleList,411RenderAssetUsages::default(),412)413.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs)414.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns)415.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts)416.with_inserted_indices(Indices::U32(tris))417}418}419420impl Meshable for Capsule3d {421type Output = Capsule3dMeshBuilder;422423fn mesh(&self) -> Self::Output {424Capsule3dMeshBuilder {425capsule: *self,426..Default::default()427}428}429}430431impl From<Capsule3d> for Mesh {432fn from(capsule: Capsule3d) -> Self {433capsule.mesh().build()434}435}436437438