Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1257
vendor/bevy_mesh/src/primitives/dim2.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,436 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3};
use bevy_reflect::prelude::*;
/// Manner in which UV coordinates are distributed vertically.
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Default, Debug, Clone)]
pub enum CapsuleUvProfile {
/// UV space is distributed by how much of the capsule consists of the hemispheres.
#[default]
Aspect,
/// Hemispheres get UV space according to the ratio of latitudes to rings.
Uniform,
/// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder
/// and lower third to the southern one.
Fixed,
}
/// A builder used for creating a [`Mesh`] with a [`Capsule3d`] shape.
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct Capsule3dMeshBuilder {
/// The [`Capsule3d`] shape.
pub capsule: Capsule3d,
/// The number of horizontal lines subdividing the cylindrical part of the capsule.
/// The default is `0`.
pub rings: u32,
/// The number of vertical lines subdividing the hemispheres of the capsule.
/// The default is `32`.
pub longitudes: u32,
/// The number of horizontal lines subdividing the hemispheres of the capsule.
/// The default is `16`.
pub latitudes: u32,
/// The manner in which UV coordinates are distributed vertically.
/// The default is [`CapsuleUvProfile::Aspect`].
pub uv_profile: CapsuleUvProfile,
}
impl Default for Capsule3dMeshBuilder {
fn default() -> Self {
Self {
capsule: Capsule3d::default(),
rings: 0,
longitudes: 32,
latitudes: 16,
uv_profile: CapsuleUvProfile::default(),
}
}
}
impl Capsule3dMeshBuilder {
/// Creates a new [`Capsule3dMeshBuilder`] from a given radius, height, longitudes, and latitudes.
///
/// Note that `height` is the distance between the centers of the hemispheres.
/// `radius` will be added to both ends to get the real height of the mesh.
#[inline]
pub fn new(radius: f32, height: f32, longitudes: u32, latitudes: u32) -> Self {
Self {
capsule: Capsule3d::new(radius, height),
longitudes,
latitudes,
..Default::default()
}
}
/// Sets the number of horizontal lines subdividing the cylindrical part of the capsule.
#[inline]
pub const fn rings(mut self, rings: u32) -> Self {
self.rings = rings;
self
}
/// Sets the number of vertical lines subdividing the hemispheres of the capsule.
#[inline]
pub const fn longitudes(mut self, longitudes: u32) -> Self {
self.longitudes = longitudes;
self
}
/// Sets the number of horizontal lines subdividing the hemispheres of the capsule.
#[inline]
pub const fn latitudes(mut self, latitudes: u32) -> Self {
self.latitudes = latitudes;
self
}
/// Sets the manner in which UV coordinates are distributed vertically.
#[inline]
pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self {
self.uv_profile = uv_profile;
self
}
}
impl MeshBuilder for Capsule3dMeshBuilder {
fn build(&self) -> Mesh {
// code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db
let Capsule3dMeshBuilder {
capsule,
rings,
longitudes,
latitudes,
uv_profile,
} = *self;
let Capsule3d {
radius,
half_length,
} = capsule;
let calc_middle = rings > 0;
let half_lats = latitudes / 2;
let half_latsn1 = half_lats - 1;
let half_latsn2 = half_lats - 2;
let ringsp1 = rings + 1;
let lonsp1 = longitudes + 1;
let summit = half_length + radius;
// Vertex index offsets.
let vert_offset_north_hemi = longitudes;
let vert_offset_north_equator = vert_offset_north_hemi + lonsp1 * half_latsn1;
let vert_offset_cylinder = vert_offset_north_equator + lonsp1;
let vert_offset_south_equator = if calc_middle {
vert_offset_cylinder + lonsp1 * rings
} else {
vert_offset_cylinder
};
let vert_offset_south_hemi = vert_offset_south_equator + lonsp1;
let vert_offset_south_polar = vert_offset_south_hemi + lonsp1 * half_latsn2;
let vert_offset_south_cap = vert_offset_south_polar + lonsp1;
// Initialize arrays.
let vert_len = (vert_offset_south_cap + longitudes) as usize;
let mut vs: Vec<Vec3> = vec![Vec3::ZERO; vert_len];
let mut vts: Vec<Vec2> = vec![Vec2::ZERO; vert_len];
let mut vns: Vec<Vec3> = vec![Vec3::ZERO; vert_len];
let to_theta = 2.0 * core::f32::consts::PI / longitudes as f32;
let to_phi = core::f32::consts::PI / latitudes as f32;
let to_tex_horizontal = 1.0 / longitudes as f32;
let to_tex_vertical = 1.0 / half_lats as f32;
let vt_aspect_ratio = match uv_profile {
CapsuleUvProfile::Aspect => radius / (2.0 * half_length + radius + radius),
CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32,
CapsuleUvProfile::Fixed => 1.0 / 3.0,
};
let vt_aspect_north = 1.0 - vt_aspect_ratio;
let vt_aspect_south = vt_aspect_ratio;
let mut theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];
let mut rho_theta_cartesian: Vec<Vec2> = vec![Vec2::ZERO; longitudes as usize];
let mut s_texture_cache: Vec<f32> = vec![0.0; lonsp1 as usize];
for j in 0..longitudes as usize {
let jf = j as f32;
let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal);
let theta = jf * to_theta;
theta_cartesian[j] = Vec2::from_angle(theta);
rho_theta_cartesian[j] = radius * theta_cartesian[j];
// North.
vs[j] = Vec3::new(0.0, summit, 0.0);
vts[j] = Vec2::new(s_texture_polar, 1.0);
vns[j] = Vec3::Y;
// South.
let idx = vert_offset_south_cap as usize + j;
vs[idx] = Vec3::new(0.0, -summit, 0.0);
vts[idx] = Vec2::new(s_texture_polar, 0.0);
vns[idx] = Vec3::new(0.0, -1.0, 0.0);
}
// Equatorial vertices.
for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1 as usize) {
let s_texture = 1.0 - j as f32 * to_tex_horizontal;
*s_texture_cache_j = s_texture;
// Wrap to first element upon reaching last.
let j_mod = j % longitudes as usize;
let tc = theta_cartesian[j_mod];
let rtc = rho_theta_cartesian[j_mod];
// North equator.
let idxn = vert_offset_north_equator as usize + j;
vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y);
vts[idxn] = Vec2::new(s_texture, vt_aspect_north);
vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y);
// South equator.
let idxs = vert_offset_south_equator as usize + j;
vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y);
vts[idxs] = Vec2::new(s_texture, vt_aspect_south);
vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y);
}
// Hemisphere vertices.
for i in 0..half_latsn1 {
let ip1f = i as f32 + 1.0;
let phi = ip1f * to_phi;
// For coordinates.
let (sin_phi_south, cos_phi_south) = ops::sin_cos(phi);
// Symmetrical hemispheres mean cosine and sine only needs
// to be calculated once.
let cos_phi_north = sin_phi_south;
let sin_phi_north = -cos_phi_south;
let rho_cos_phi_north = radius * cos_phi_north;
let rho_sin_phi_north = radius * sin_phi_north;
let z_offset_north = half_length - rho_sin_phi_north;
let rho_cos_phi_south = radius * cos_phi_south;
let rho_sin_phi_south = radius * sin_phi_south;
let z_offset_sout = -half_length - rho_sin_phi_south;
// For texture coordinates.
let t_tex_fac = ip1f * to_tex_vertical;
let cmpl_tex_fac = 1.0 - t_tex_fac;
let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac;
let t_tex_south = cmpl_tex_fac * vt_aspect_south;
let i_lonsp1 = i * lonsp1;
let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1;
for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {
let j_mod = j % longitudes as usize;
let tc = theta_cartesian[j_mod];
// North hemisphere.
let idxn = vert_curr_lat_north as usize + j;
vs[idxn] = Vec3::new(
rho_cos_phi_north * tc.x,
z_offset_north,
-rho_cos_phi_north * tc.y,
);
vts[idxn] = Vec2::new(*s_texture, t_tex_north);
vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y);
// South hemisphere.
let idxs = vert_curr_lat_south as usize + j;
vs[idxs] = Vec3::new(
rho_cos_phi_south * tc.x,
z_offset_sout,
-rho_cos_phi_south * tc.y,
);
vts[idxs] = Vec2::new(*s_texture, t_tex_south);
vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y);
}
}
// Cylinder vertices.
if calc_middle {
// Exclude both origin and destination edges
// (North and South equators) from the interpolation.
let to_fac = 1.0 / ringsp1 as f32;
let mut idx_cyl_lat = vert_offset_cylinder as usize;
for h in 1..ringsp1 {
let fac = h as f32 * to_fac;
let cmpl_fac = 1.0 - fac;
let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south;
let z = half_length - 2.0 * half_length * fac;
for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1 as usize) {
let j_mod = j % longitudes as usize;
let tc = theta_cartesian[j_mod];
let rtc = rho_theta_cartesian[j_mod];
vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y);
vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture);
vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y);
idx_cyl_lat += 1;
}
}
}
// Triangle indices.
// Stride is 3 for polar triangles;
// stride is 6 for two triangles forming a quad.
let lons3 = longitudes * 3;
let lons6 = longitudes * 6;
let hemi_lons = half_latsn1 * lons6;
let tri_offset_north_hemi = lons3;
let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons;
let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6;
let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons;
let fs_len = tri_offset_south_cap + lons3;
let mut tris: Vec<u32> = vec![0; fs_len as usize];
// Polar caps.
let mut i = 0;
let mut k = 0;
let mut m = tri_offset_south_cap as usize;
while i < longitudes {
// North.
tris[k] = i;
tris[k + 1] = vert_offset_north_hemi + i;
tris[k + 2] = vert_offset_north_hemi + i + 1;
// South.
tris[m] = vert_offset_south_cap + i;
tris[m + 1] = vert_offset_south_polar + i + 1;
tris[m + 2] = vert_offset_south_polar + i;
i += 1;
k += 3;
m += 3;
}
// Hemispheres.
let mut i = 0;
let mut k = tri_offset_north_hemi as usize;
let mut m = tri_offset_south_hemi as usize;
while i < half_latsn1 {
let i_lonsp1 = i * lonsp1;
let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1;
let vert_next_lat_north = vert_curr_lat_north + lonsp1;
let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1;
let vert_next_lat_south = vert_curr_lat_south + lonsp1;
let mut j = 0;
while j < longitudes {
// North.
let north00 = vert_curr_lat_north + j;
let north01 = vert_next_lat_north + j;
let north11 = vert_next_lat_north + j + 1;
let north10 = vert_curr_lat_north + j + 1;
tris[k] = north00;
tris[k + 1] = north11;
tris[k + 2] = north10;
tris[k + 3] = north00;
tris[k + 4] = north01;
tris[k + 5] = north11;
// South.
let south00 = vert_curr_lat_south + j;
let south01 = vert_next_lat_south + j;
let south11 = vert_next_lat_south + j + 1;
let south10 = vert_curr_lat_south + j + 1;
tris[m] = south00;
tris[m + 1] = south11;
tris[m + 2] = south10;
tris[m + 3] = south00;
tris[m + 4] = south01;
tris[m + 5] = south11;
j += 1;
k += 6;
m += 6;
}
i += 1;
}
// Cylinder.
let mut i = 0;
let mut k = tri_offset_cylinder as usize;
while i < ringsp1 {
let vert_curr_lat = vert_offset_north_equator + i * lonsp1;
let vert_next_lat = vert_curr_lat + lonsp1;
let mut j = 0;
while j < longitudes {
let cy00 = vert_curr_lat + j;
let cy01 = vert_next_lat + j;
let cy11 = vert_next_lat + j + 1;
let cy10 = vert_curr_lat + j + 1;
tris[k] = cy00;
tris[k + 1] = cy11;
tris[k + 2] = cy10;
tris[k + 3] = cy00;
tris[k + 4] = cy01;
tris[k + 5] = cy11;
j += 1;
k += 6;
}
i += 1;
}
let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect();
let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect();
let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect();
assert_eq!(vs.len(), vert_len);
assert_eq!(tris.len(), fs_len as usize);
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts)
.with_inserted_indices(Indices::U32(tris))
}
}
impl Meshable for Capsule3d {
type Output = Capsule3dMeshBuilder;
fn mesh(&self) -> Self::Output {
Capsule3dMeshBuilder {
capsule: *self,
..Default::default()
}
}
}
impl From<Capsule3d> for Mesh {
fn from(capsule: Capsule3d) -> Self {
capsule.mesh().build()
}
}

View File

@@ -0,0 +1,271 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{ops, primitives::Cone, Vec3};
use bevy_reflect::prelude::*;
/// Anchoring options for [`ConeMeshBuilder`]
#[derive(Debug, Copy, Clone, Default, Reflect)]
#[reflect(Default, Debug, Clone)]
pub enum ConeAnchor {
#[default]
/// Midpoint between the tip of the cone and the center of its base.
MidPoint,
/// The Tip of the triangle
Tip,
/// The center of the base circle
Base,
}
/// A builder used for creating a [`Mesh`] with a [`Cone`] shape.
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct ConeMeshBuilder {
/// The [`Cone`] shape.
pub cone: Cone,
/// The number of vertices used for the base of the cone.
///
/// The default is `32`.
pub resolution: u32,
/// The anchor point for the cone mesh, defaults to the midpoint between
/// the tip of the cone and the center of its base
pub anchor: ConeAnchor,
}
impl Default for ConeMeshBuilder {
fn default() -> Self {
Self {
cone: Cone::default(),
resolution: 32,
anchor: ConeAnchor::default(),
}
}
}
impl ConeMeshBuilder {
/// Creates a new [`ConeMeshBuilder`] from a given radius, height,
/// and number of vertices used for the base of the cone.
#[inline]
pub const fn new(radius: f32, height: f32, resolution: u32) -> Self {
Self {
cone: Cone { radius, height },
resolution,
anchor: ConeAnchor::MidPoint,
}
}
/// Sets the number of vertices used for the base of the cone.
#[inline]
pub const fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
/// Sets a custom anchor point for the mesh
#[inline]
pub const fn anchor(mut self, anchor: ConeAnchor) -> Self {
self.anchor = anchor;
self
}
}
impl MeshBuilder for ConeMeshBuilder {
fn build(&self) -> Mesh {
let half_height = self.cone.height / 2.0;
// `resolution` vertices for the base, `resolution` vertices for the bottom of the lateral surface,
// and one vertex for the tip.
let num_vertices = self.resolution as usize * 2 + 1;
let num_indices = self.resolution as usize * 6 - 6;
let mut positions = Vec::with_capacity(num_vertices);
let mut normals = Vec::with_capacity(num_vertices);
let mut uvs = Vec::with_capacity(num_vertices);
let mut indices = Vec::with_capacity(num_indices);
// Tip
positions.push([0.0, half_height, 0.0]);
// The tip doesn't have a singular normal that works correctly.
// We use an invalid normal here so that it becomes NaN in the fragment shader
// and doesn't affect the overall shading. This might seem hacky, but it's one of
// the only ways to get perfectly smooth cones without creases or other shading artifacts.
//
// Note that this requires that normals are not normalized in the vertex shader,
// as that would make the entire triangle invalid and make the cone appear as black.
normals.push([0.0, 0.0, 0.0]);
// The UVs of the cone are in polar coordinates, so it's like projecting a circle texture from above.
// The center of the texture is at the center of the lateral surface, at the tip of the cone.
uvs.push([0.5, 0.5]);
// Now we build the lateral surface, the side of the cone.
// The vertex normals will be perpendicular to the surface.
//
// Here we get the slope of a normal and use it for computing
// the multiplicative inverse of the length of a vector in the direction
// of the normal. This allows us to normalize vertex normals efficiently.
let normal_slope = self.cone.radius / self.cone.height;
// Equivalent to Vec2::new(1.0, slope).length().recip()
let normalization_factor = (1.0 + normal_slope * normal_slope).sqrt().recip();
// How much the angle changes at each step
let step_theta = core::f32::consts::TAU / self.resolution as f32;
// Add vertices for the bottom of the lateral surface.
for segment in 0..self.resolution {
let theta = segment as f32 * step_theta;
let (sin, cos) = ops::sin_cos(theta);
// The vertex normal perpendicular to the side
let normal = Vec3::new(cos, normal_slope, sin) * normalization_factor;
positions.push([self.cone.radius * cos, -half_height, self.cone.radius * sin]);
normals.push(normal.to_array());
uvs.push([0.5 + cos * 0.5, 0.5 + sin * 0.5]);
}
// Add indices for the lateral surface. Each triangle is formed by the tip
// and two vertices at the base.
for j in 1..self.resolution {
indices.extend_from_slice(&[0, j + 1, j]);
}
// Close the surface with a triangle between the tip, first base vertex, and last base vertex.
indices.extend_from_slice(&[0, 1, self.resolution]);
// Now we build the actual base of the cone.
let index_offset = positions.len() as u32;
// Add base vertices.
for i in 0..self.resolution {
let theta = i as f32 * step_theta;
let (sin, cos) = ops::sin_cos(theta);
positions.push([cos * self.cone.radius, -half_height, sin * self.cone.radius]);
normals.push([0.0, -1.0, 0.0]);
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
}
// Add base indices.
for i in 1..(self.resolution - 1) {
indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]);
}
// Offset the vertex positions Y axis to match the anchor
match self.anchor {
ConeAnchor::Tip => positions.iter_mut().for_each(|p| p[1] -= half_height),
ConeAnchor::Base => positions.iter_mut().for_each(|p| p[1] += half_height),
ConeAnchor::MidPoint => (),
};
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl Meshable for Cone {
type Output = ConeMeshBuilder;
fn mesh(&self) -> Self::Output {
ConeMeshBuilder {
cone: *self,
..Default::default()
}
}
}
impl From<Cone> for Mesh {
fn from(cone: Cone) -> Self {
cone.mesh().build()
}
}
#[cfg(test)]
mod tests {
use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
use bevy_math::{primitives::Cone, Vec2};
/// Rounds floats to handle floating point error in tests.
fn round_floats<const N: usize>(points: &mut [[f32; N]]) {
for point in points.iter_mut() {
for coord in point.iter_mut() {
let round = (*coord * 100.0).round() / 100.0;
if (*coord - round).abs() < 0.00001 {
*coord = round;
}
}
}
}
#[test]
fn cone_mesh() {
let mut mesh = Cone {
radius: 0.5,
height: 1.0,
}
.mesh()
.resolution(4)
.build();
let Some(VertexAttributeValues::Float32x3(mut positions)) =
mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
else {
panic!("Expected positions f32x3");
};
let Some(VertexAttributeValues::Float32x3(mut normals)) =
mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
else {
panic!("Expected normals f32x3");
};
round_floats(&mut positions);
round_floats(&mut normals);
// Vertex positions
assert_eq!(
[
// Tip
[0.0, 0.5, 0.0],
// Lateral surface
[0.5, -0.5, 0.0],
[0.0, -0.5, 0.5],
[-0.5, -0.5, 0.0],
[0.0, -0.5, -0.5],
// Base
[0.5, -0.5, 0.0],
[0.0, -0.5, 0.5],
[-0.5, -0.5, 0.0],
[0.0, -0.5, -0.5],
],
&positions[..]
);
// Vertex normals
let [x, y] = Vec2::new(0.5, -1.0).perp().normalize().to_array();
assert_eq!(
&[
// Tip
[0.0, 0.0, 0.0],
// Lateral surface
[x, y, 0.0],
[0.0, y, x],
[-x, y, 0.0],
[0.0, y, -x],
// Base
[0.0, -1.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, -1.0, 0.0],
],
&normals[..]
);
}
}

View File

@@ -0,0 +1,184 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{ops, primitives::ConicalFrustum, Vec3};
use bevy_reflect::prelude::*;
/// A builder used for creating a [`Mesh`] with a [`ConicalFrustum`] shape.
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct ConicalFrustumMeshBuilder {
/// The [`ConicalFrustum`] shape.
pub frustum: ConicalFrustum,
/// The number of vertices used for the top and bottom of the conical frustum.
///
/// The default is `32`.
pub resolution: u32,
/// The number of horizontal lines subdividing the lateral surface of the conical frustum.
///
/// The default is `1`.
pub segments: u32,
}
impl Default for ConicalFrustumMeshBuilder {
fn default() -> Self {
Self {
frustum: ConicalFrustum::default(),
resolution: 32,
segments: 1,
}
}
}
impl ConicalFrustumMeshBuilder {
/// Creates a new [`ConicalFrustumMeshBuilder`] from the given top and bottom radii, a height,
/// and a resolution used for the top and bottom.
#[inline]
pub const fn new(radius_top: f32, radius_bottom: f32, height: f32, resolution: u32) -> Self {
Self {
frustum: ConicalFrustum {
radius_top,
radius_bottom,
height,
},
resolution,
segments: 1,
}
}
/// Sets the number of vertices used for the top and bottom of the conical frustum.
#[inline]
pub const fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
/// Sets the number of horizontal lines subdividing the lateral surface of the conical frustum.
#[inline]
pub const fn segments(mut self, segments: u32) -> Self {
self.segments = segments;
self
}
}
impl MeshBuilder for ConicalFrustumMeshBuilder {
fn build(&self) -> Mesh {
debug_assert!(self.resolution > 2);
debug_assert!(self.segments > 0);
let ConicalFrustum {
radius_top,
radius_bottom,
height,
} = self.frustum;
let half_height = height / 2.0;
let num_rings = self.segments + 1;
let num_vertices = (self.resolution * 2 + num_rings * (self.resolution + 1)) as usize;
let num_faces = self.resolution * (num_rings - 2);
let num_indices = ((2 * num_faces + 2 * (self.resolution - 1) * 2) * 3) as usize;
let mut positions = Vec::with_capacity(num_vertices);
let mut normals = Vec::with_capacity(num_vertices);
let mut uvs = Vec::with_capacity(num_vertices);
let mut indices = Vec::with_capacity(num_indices);
let step_theta = core::f32::consts::TAU / self.resolution as f32;
let step_y = height / self.segments as f32;
let step_radius = (radius_top - radius_bottom) / self.segments as f32;
// Rings
for ring in 0..num_rings {
let y = -half_height + ring as f32 * step_y;
let radius = radius_bottom + ring as f32 * step_radius;
for segment in 0..=self.resolution {
let theta = segment as f32 * step_theta;
let (sin, cos) = ops::sin_cos(theta);
positions.push([radius * cos, y, radius * sin]);
normals.push(
Vec3::new(cos, (radius_bottom - radius_top) / height, sin)
.normalize()
.to_array(),
);
uvs.push([
segment as f32 / self.resolution as f32,
ring as f32 / self.segments as f32,
]);
}
}
// Lateral surface
for i in 0..self.segments {
let ring = i * (self.resolution + 1);
let next_ring = (i + 1) * (self.resolution + 1);
for j in 0..self.resolution {
indices.extend_from_slice(&[
ring + j,
next_ring + j,
ring + j + 1,
next_ring + j,
next_ring + j + 1,
ring + j + 1,
]);
}
}
// Caps
let mut build_cap = |top: bool, radius: f32| {
let offset = positions.len() as u32;
let (y, normal_y, winding) = if top {
(half_height, 1.0, (1, 0))
} else {
(-half_height, -1.0, (0, 1))
};
for i in 0..self.resolution {
let theta = i as f32 * step_theta;
let (sin, cos) = ops::sin_cos(theta);
positions.push([cos * radius, y, sin * radius]);
normals.push([0.0, normal_y, 0.0]);
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
}
for i in 1..(self.resolution - 1) {
indices.extend_from_slice(&[
offset,
offset + i + winding.0,
offset + i + winding.1,
]);
}
};
build_cap(true, radius_top);
build_cap(false, radius_bottom);
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl Meshable for ConicalFrustum {
type Output = ConicalFrustumMeshBuilder;
fn mesh(&self) -> Self::Output {
ConicalFrustumMeshBuilder {
frustum: *self,
..Default::default()
}
}
}
impl From<ConicalFrustum> for Mesh {
fn from(frustum: ConicalFrustum) -> Self {
frustum.mesh().build()
}
}

View File

@@ -0,0 +1,99 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{primitives::Cuboid, Vec3};
use bevy_reflect::prelude::*;
/// A builder used for creating a [`Mesh`] with a [`Cuboid`] shape.
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct CuboidMeshBuilder {
half_size: Vec3,
}
impl Default for CuboidMeshBuilder {
/// Returns the default [`CuboidMeshBuilder`] with a width, height, and depth of `1.0`.
fn default() -> Self {
Self {
half_size: Vec3::splat(0.5),
}
}
}
impl MeshBuilder for CuboidMeshBuilder {
fn build(&self) -> Mesh {
let min = -self.half_size;
let max = self.half_size;
// Suppose Y-up right hand, and camera look from +Z to -Z
let vertices = &[
// Front
([min.x, min.y, max.z], [0.0, 0.0, 1.0], [0.0, 0.0]),
([max.x, min.y, max.z], [0.0, 0.0, 1.0], [1.0, 0.0]),
([max.x, max.y, max.z], [0.0, 0.0, 1.0], [1.0, 1.0]),
([min.x, max.y, max.z], [0.0, 0.0, 1.0], [0.0, 1.0]),
// Back
([min.x, max.y, min.z], [0.0, 0.0, -1.0], [1.0, 0.0]),
([max.x, max.y, min.z], [0.0, 0.0, -1.0], [0.0, 0.0]),
([max.x, min.y, min.z], [0.0, 0.0, -1.0], [0.0, 1.0]),
([min.x, min.y, min.z], [0.0, 0.0, -1.0], [1.0, 1.0]),
// Right
([max.x, min.y, min.z], [1.0, 0.0, 0.0], [0.0, 0.0]),
([max.x, max.y, min.z], [1.0, 0.0, 0.0], [1.0, 0.0]),
([max.x, max.y, max.z], [1.0, 0.0, 0.0], [1.0, 1.0]),
([max.x, min.y, max.z], [1.0, 0.0, 0.0], [0.0, 1.0]),
// Left
([min.x, min.y, max.z], [-1.0, 0.0, 0.0], [1.0, 0.0]),
([min.x, max.y, max.z], [-1.0, 0.0, 0.0], [0.0, 0.0]),
([min.x, max.y, min.z], [-1.0, 0.0, 0.0], [0.0, 1.0]),
([min.x, min.y, min.z], [-1.0, 0.0, 0.0], [1.0, 1.0]),
// Top
([max.x, max.y, min.z], [0.0, 1.0, 0.0], [1.0, 0.0]),
([min.x, max.y, min.z], [0.0, 1.0, 0.0], [0.0, 0.0]),
([min.x, max.y, max.z], [0.0, 1.0, 0.0], [0.0, 1.0]),
([max.x, max.y, max.z], [0.0, 1.0, 0.0], [1.0, 1.0]),
// Bottom
([max.x, min.y, max.z], [0.0, -1.0, 0.0], [0.0, 0.0]),
([min.x, min.y, max.z], [0.0, -1.0, 0.0], [1.0, 0.0]),
([min.x, min.y, min.z], [0.0, -1.0, 0.0], [1.0, 1.0]),
([max.x, min.y, min.z], [0.0, -1.0, 0.0], [0.0, 1.0]),
];
let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect();
let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect();
let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect();
let indices = Indices::U32(vec![
0, 1, 2, 2, 3, 0, // front
4, 5, 6, 6, 7, 4, // back
8, 9, 10, 10, 11, 8, // right
12, 13, 14, 14, 15, 12, // left
16, 17, 18, 18, 19, 16, // top
20, 21, 22, 22, 23, 20, // bottom
]);
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
.with_inserted_indices(indices)
}
}
impl Meshable for Cuboid {
type Output = CuboidMeshBuilder;
fn mesh(&self) -> Self::Output {
CuboidMeshBuilder {
half_size: self.half_size,
}
}
}
impl From<Cuboid> for Mesh {
fn from(cuboid: Cuboid) -> Self {
cuboid.mesh().build()
}
}

View File

@@ -0,0 +1,222 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{ops, primitives::Cylinder};
use bevy_reflect::prelude::*;
/// Anchoring options for [`CylinderMeshBuilder`]
#[derive(Debug, Copy, Clone, Default, Reflect)]
#[reflect(Default, Debug, Clone)]
pub enum CylinderAnchor {
#[default]
/// Midpoint between the top and bottom caps of the cylinder
MidPoint,
/// The center of the top circle cap
Top,
/// The center of the bottom circle cap
Bottom,
}
/// A builder used for creating a [`Mesh`] with a [`Cylinder`] shape.
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct CylinderMeshBuilder {
/// The [`Cylinder`] shape.
pub cylinder: Cylinder,
/// The number of vertices used for the top and bottom of the cylinder.
///
/// The default is `32`.
pub resolution: u32,
/// The number of segments along the height of the cylinder.
/// Must be greater than `0` for geometry to be generated.
///
/// The default is `1`.
pub segments: u32,
/// If set to `true`, the cylinder caps (flat circle faces) are built,
/// otherwise the mesh will be a shallow tube
pub caps: bool,
/// The anchor point for the cylinder mesh, defaults to the midpoint between
/// the top and bottom caps
pub anchor: CylinderAnchor,
}
impl Default for CylinderMeshBuilder {
fn default() -> Self {
Self {
cylinder: Cylinder::default(),
resolution: 32,
segments: 1,
caps: true,
anchor: CylinderAnchor::default(),
}
}
}
impl CylinderMeshBuilder {
/// Creates a new [`CylinderMeshBuilder`] from the given radius, a height,
/// and a resolution used for the top and bottom.
#[inline]
pub fn new(radius: f32, height: f32, resolution: u32) -> Self {
Self {
cylinder: Cylinder::new(radius, height),
resolution,
..Default::default()
}
}
/// Sets the number of vertices used for the top and bottom of the cylinder.
#[inline]
pub const fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
/// Sets the number of segments along the height of the cylinder.
/// Must be greater than `0` for geometry to be generated.
#[inline]
pub const fn segments(mut self, segments: u32) -> Self {
self.segments = segments;
self
}
/// Ignore the cylinder caps, making the mesh a shallow tube instead
#[inline]
pub const fn without_caps(mut self) -> Self {
self.caps = false;
self
}
/// Sets a custom anchor point for the mesh
#[inline]
pub const fn anchor(mut self, anchor: CylinderAnchor) -> Self {
self.anchor = anchor;
self
}
}
impl MeshBuilder for CylinderMeshBuilder {
fn build(&self) -> Mesh {
let resolution = self.resolution;
let segments = self.segments;
debug_assert!(resolution > 2);
debug_assert!(segments > 0);
let num_rings = segments + 1;
let num_vertices = resolution * 2 + num_rings * (resolution + 1);
let num_faces = resolution * (num_rings - 2);
let num_indices = (2 * num_faces + 2 * (resolution - 1) * 2) * 3;
let mut positions = Vec::with_capacity(num_vertices as usize);
let mut normals = Vec::with_capacity(num_vertices as usize);
let mut uvs = Vec::with_capacity(num_vertices as usize);
let mut indices = Vec::with_capacity(num_indices as usize);
let step_theta = core::f32::consts::TAU / resolution as f32;
let step_y = 2.0 * self.cylinder.half_height / segments as f32;
// rings
for ring in 0..num_rings {
let y = -self.cylinder.half_height + ring as f32 * step_y;
for segment in 0..=resolution {
let theta = segment as f32 * step_theta;
let (sin, cos) = ops::sin_cos(theta);
positions.push([self.cylinder.radius * cos, y, self.cylinder.radius * sin]);
normals.push([cos, 0., sin]);
uvs.push([
segment as f32 / resolution as f32,
ring as f32 / segments as f32,
]);
}
}
// barrel skin
for i in 0..segments {
let ring = i * (resolution + 1);
let next_ring = (i + 1) * (resolution + 1);
for j in 0..resolution {
indices.extend_from_slice(&[
ring + j,
next_ring + j,
ring + j + 1,
next_ring + j,
next_ring + j + 1,
ring + j + 1,
]);
}
}
// caps
if self.caps {
let mut build_cap = |top: bool| {
let offset = positions.len() as u32;
let (y, normal_y, winding) = if top {
(self.cylinder.half_height, 1., (1, 0))
} else {
(-self.cylinder.half_height, -1., (0, 1))
};
for i in 0..self.resolution {
let theta = i as f32 * step_theta;
let (sin, cos) = ops::sin_cos(theta);
positions.push([cos * self.cylinder.radius, y, sin * self.cylinder.radius]);
normals.push([0.0, normal_y, 0.0]);
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
}
for i in 1..(self.resolution - 1) {
indices.extend_from_slice(&[
offset,
offset + i + winding.0,
offset + i + winding.1,
]);
}
};
build_cap(true);
build_cap(false);
}
// Offset the vertex positions Y axis to match the anchor
match self.anchor {
CylinderAnchor::Top => positions
.iter_mut()
.for_each(|p| p[1] -= self.cylinder.half_height),
CylinderAnchor::Bottom => positions
.iter_mut()
.for_each(|p| p[1] += self.cylinder.half_height),
CylinderAnchor::MidPoint => (),
};
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl Meshable for Cylinder {
type Output = CylinderMeshBuilder;
fn mesh(&self) -> Self::Output {
CylinderMeshBuilder {
cylinder: *self,
..Default::default()
}
}
}
impl From<Cylinder> for Mesh {
fn from(cylinder: Cylinder) -> Self {
cylinder.mesh().build()
}
}

View File

@@ -0,0 +1,21 @@
mod capsule;
mod cone;
mod conical_frustum;
mod cuboid;
mod cylinder;
mod plane;
mod sphere;
mod tetrahedron;
mod torus;
pub(crate) mod triangle3d;
pub use capsule::*;
pub use cone::*;
pub use conical_frustum::*;
pub use cuboid::*;
pub use cylinder::*;
pub use plane::*;
pub use sphere::*;
pub use tetrahedron::*;
pub use torus::*;
pub use triangle3d::*;

View File

@@ -0,0 +1,160 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{primitives::Plane3d, Dir3, Quat, Vec2, Vec3};
use bevy_reflect::prelude::*;
/// A builder used for creating a [`Mesh`] with a [`Plane3d`] shape.
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct PlaneMeshBuilder {
/// The [`Plane3d`] shape.
pub plane: Plane3d,
/// The number of subdivisions in the mesh.
///
/// 0 - is the original plane geometry, the 4 points in the XZ plane.
///
/// 1 - is split by 1 line in the middle of the plane on both the X axis and the Z axis, resulting in a plane with 4 quads / 8 triangles.
///
/// 2 - is a plane split by 2 lines on both the X and Z axes, subdividing the plane into 3 equal sections along each axis, resulting in a plane with 9 quads / 18 triangles.
///
/// and so on...
pub subdivisions: u32,
}
impl PlaneMeshBuilder {
/// Creates a new [`PlaneMeshBuilder`] from a given normal and size.
#[inline]
pub fn new(normal: Dir3, size: Vec2) -> Self {
Self {
plane: Plane3d {
normal,
half_size: size / 2.0,
},
subdivisions: 0,
}
}
/// Creates a new [`PlaneMeshBuilder`] from the given size, with the normal pointing upwards.
#[inline]
pub fn from_size(size: Vec2) -> Self {
Self {
plane: Plane3d {
half_size: size / 2.0,
..Default::default()
},
subdivisions: 0,
}
}
/// Creates a new [`PlaneMeshBuilder`] from the given length, with the normal pointing upwards,
/// and the resulting [`PlaneMeshBuilder`] being a square.
#[inline]
pub fn from_length(length: f32) -> Self {
Self {
plane: Plane3d {
half_size: Vec2::splat(length) / 2.0,
..Default::default()
},
subdivisions: 0,
}
}
/// Sets the normal of the plane, aka the direction the plane is facing.
#[inline]
#[doc(alias = "facing")]
pub fn normal(mut self, normal: Dir3) -> Self {
self.plane = Plane3d {
normal,
..self.plane
};
self
}
/// Sets the size of the plane mesh.
#[inline]
pub fn size(mut self, width: f32, height: f32) -> Self {
self.plane.half_size = Vec2::new(width, height) / 2.0;
self
}
/// Sets the subdivisions of the plane mesh.
///
/// 0 - is the original plane geometry, the 4 points in the XZ plane.
///
/// 1 - is split by 1 line in the middle of the plane on both the X axis and the Z axis,
/// resulting in a plane with 4 quads / 8 triangles.
///
/// 2 - is a plane split by 2 lines on both the X and Z axes, subdividing the plane into 3
/// equal sections along each axis, resulting in a plane with 9 quads / 18 triangles.
#[inline]
pub fn subdivisions(mut self, subdivisions: u32) -> Self {
self.subdivisions = subdivisions;
self
}
}
impl MeshBuilder for PlaneMeshBuilder {
fn build(&self) -> Mesh {
let z_vertex_count = self.subdivisions + 2;
let x_vertex_count = self.subdivisions + 2;
let num_vertices = (z_vertex_count * x_vertex_count) as usize;
let num_indices = ((z_vertex_count - 1) * (x_vertex_count - 1) * 6) as usize;
let mut positions: Vec<Vec3> = Vec::with_capacity(num_vertices);
let mut normals: Vec<[f32; 3]> = Vec::with_capacity(num_vertices);
let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(num_vertices);
let mut indices: Vec<u32> = Vec::with_capacity(num_indices);
let rotation = Quat::from_rotation_arc(Vec3::Y, *self.plane.normal);
let size = self.plane.half_size * 2.0;
for z in 0..z_vertex_count {
for x in 0..x_vertex_count {
let tx = x as f32 / (x_vertex_count - 1) as f32;
let tz = z as f32 / (z_vertex_count - 1) as f32;
let pos = rotation * Vec3::new((-0.5 + tx) * size.x, 0.0, (-0.5 + tz) * size.y);
positions.push(pos);
normals.push(self.plane.normal.to_array());
uvs.push([tx, tz]);
}
}
for z in 0..z_vertex_count - 1 {
for x in 0..x_vertex_count - 1 {
let quad = z * x_vertex_count + x;
indices.push(quad + x_vertex_count + 1);
indices.push(quad + 1);
indices.push(quad + x_vertex_count);
indices.push(quad);
indices.push(quad + x_vertex_count);
indices.push(quad + 1);
}
}
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl Meshable for Plane3d {
type Output = PlaneMeshBuilder;
fn mesh(&self) -> Self::Output {
PlaneMeshBuilder {
plane: *self,
subdivisions: 0,
}
}
}
impl From<Plane3d> for Mesh {
fn from(plane: Plane3d) -> Self {
plane.mesh().build()
}
}

View File

@@ -0,0 +1,264 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{ops, primitives::Sphere};
use bevy_reflect::prelude::*;
use core::f32::consts::PI;
use hexasphere::shapes::IcoSphere;
use thiserror::Error;
/// An error when creating an icosphere [`Mesh`] from a [`SphereMeshBuilder`].
#[derive(Clone, Copy, Debug, Error)]
pub enum IcosphereError {
/// The icosphere has too many vertices.
#[error("Cannot create an icosphere of {subdivisions} subdivisions due to there being too many vertices being generated: {number_of_resulting_points}. (Limited to 65535 vertices or 79 subdivisions)")]
TooManyVertices {
/// The number of subdivisions used. 79 is the largest allowed value for a mesh to be generated.
subdivisions: u32,
/// The number of vertices generated. 65535 is the largest allowed value for a mesh to be generated.
number_of_resulting_points: u32,
},
}
/// A type of sphere mesh.
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Default, Debug, Clone)]
pub enum SphereKind {
/// An icosphere, a spherical mesh that consists of similar sized triangles.
Ico {
/// The number of subdivisions applied.
/// The number of faces quadruples with each subdivision.
subdivisions: u32,
},
/// A UV sphere, a spherical mesh that consists of quadrilaterals
/// apart from triangles at the top and bottom.
Uv {
/// The number of longitudinal sectors, aka the horizontal resolution.
#[doc(alias = "horizontal_resolution")]
sectors: u32,
/// The number of latitudinal stacks, aka the vertical resolution.
#[doc(alias = "vertical_resolution")]
stacks: u32,
},
}
impl Default for SphereKind {
fn default() -> Self {
Self::Ico { subdivisions: 5 }
}
}
/// A builder used for creating a [`Mesh`] with an [`Sphere`] shape.
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct SphereMeshBuilder {
/// The [`Sphere`] shape.
pub sphere: Sphere,
/// The type of sphere mesh that will be built.
pub kind: SphereKind,
}
impl SphereMeshBuilder {
/// Creates a new [`SphereMeshBuilder`] from a radius and [`SphereKind`].
#[inline]
pub const fn new(radius: f32, kind: SphereKind) -> Self {
Self {
sphere: Sphere { radius },
kind,
}
}
/// Sets the [`SphereKind`] that will be used for building the mesh.
#[inline]
pub const fn kind(mut self, kind: SphereKind) -> Self {
self.kind = kind;
self
}
/// Creates an icosphere mesh with the given number of subdivisions.
///
/// The number of faces quadruples with each subdivision.
/// If there are `80` or more subdivisions, the vertex count will be too large,
/// and an [`IcosphereError`] is returned.
///
/// A good default is `5` subdivisions.
pub fn ico(&self, subdivisions: u32) -> Result<Mesh, IcosphereError> {
if subdivisions >= 80 {
/*
Number of triangles:
N = 20
Number of edges:
E = 30
Number of vertices:
V = 12
Number of points within a triangle (triangular numbers):
inner(s) = (s^2 + s) / 2
Number of points on an edge:
edges(s) = s
Add up all vertices on the surface:
vertices(s) = edges(s) * E + inner(s - 1) * N + V
Expand and simplify. Notice that the triangular number formula has roots at -1, and 0, so translating it one to the right fixes it.
subdivisions(s) = 30s + 20((s^2 - 2s + 1 + s - 1) / 2) + 12
subdivisions(s) = 30s + 10s^2 - 10s + 12
subdivisions(s) = 10(s^2 + 2s) + 12
Factor an (s + 1) term to simplify in terms of calculation
subdivisions(s) = 10(s + 1)^2 + 12 - 10
resulting_vertices(s) = 10(s + 1)^2 + 2
*/
let temp = subdivisions + 1;
let number_of_resulting_points = temp * temp * 10 + 2;
return Err(IcosphereError::TooManyVertices {
subdivisions,
number_of_resulting_points,
});
}
let generated = IcoSphere::new(subdivisions as usize, |point| {
let inclination = ops::acos(point.y);
let azimuth = ops::atan2(point.z, point.x);
let norm_inclination = inclination / PI;
let norm_azimuth = 0.5 - (azimuth / core::f32::consts::TAU);
[norm_azimuth, norm_inclination]
});
let raw_points = generated.raw_points();
let points = raw_points
.iter()
.map(|&p| (p * self.sphere.radius).into())
.collect::<Vec<[f32; 3]>>();
let normals = raw_points
.iter()
.copied()
.map(Into::into)
.collect::<Vec<[f32; 3]>>();
let uvs = generated.raw_data().to_owned();
let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20);
for i in 0..20 {
generated.get_indices(i, &mut indices);
}
let indices = Indices::U32(indices);
Ok(Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(indices)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs))
}
/// Creates a UV sphere [`Mesh`] with the given number of
/// longitudinal sectors and latitudinal stacks, aka horizontal and vertical resolution.
///
/// A good default is `32` sectors and `18` stacks.
pub fn uv(&self, sectors: u32, stacks: u32) -> Mesh {
// Largely inspired from http://www.songho.ca/opengl/gl_sphere.html
let sectors_f32 = sectors as f32;
let stacks_f32 = stacks as f32;
let length_inv = 1. / self.sphere.radius;
let sector_step = 2. * PI / sectors_f32;
let stack_step = PI / stacks_f32;
let n_vertices = (stacks * sectors) as usize;
let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
let mut indices: Vec<u32> = Vec::with_capacity(n_vertices * 2 * 3);
for i in 0..stacks + 1 {
let stack_angle = PI / 2. - (i as f32) * stack_step;
let xy = self.sphere.radius * ops::cos(stack_angle);
let z = self.sphere.radius * ops::sin(stack_angle);
for j in 0..sectors + 1 {
let sector_angle = (j as f32) * sector_step;
let x = xy * ops::cos(sector_angle);
let y = xy * ops::sin(sector_angle);
vertices.push([x, y, z]);
normals.push([x * length_inv, y * length_inv, z * length_inv]);
uvs.push([(j as f32) / sectors_f32, (i as f32) / stacks_f32]);
}
}
// indices
// k1--k1+1
// | / |
// | / |
// k2--k2+1
for i in 0..stacks {
let mut k1 = i * (sectors + 1);
let mut k2 = k1 + sectors + 1;
for _j in 0..sectors {
if i != 0 {
indices.push(k1);
indices.push(k2);
indices.push(k1 + 1);
}
if i != stacks - 1 {
indices.push(k1 + 1);
indices.push(k2);
indices.push(k2 + 1);
}
k1 += 1;
k2 += 1;
}
}
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl MeshBuilder for SphereMeshBuilder {
/// Builds a [`Mesh`] according to the configuration in `self`.
///
/// # Panics
///
/// Panics if the sphere is a [`SphereKind::Ico`] with a subdivision count
/// that is greater than or equal to `80` because there will be too many vertices.
fn build(&self) -> Mesh {
match self.kind {
SphereKind::Ico { subdivisions } => self.ico(subdivisions).unwrap(),
SphereKind::Uv { sectors, stacks } => self.uv(sectors, stacks),
}
}
}
impl Meshable for Sphere {
type Output = SphereMeshBuilder;
fn mesh(&self) -> Self::Output {
SphereMeshBuilder {
sphere: *self,
..Default::default()
}
}
}
impl From<Sphere> for Mesh {
fn from(sphere: Sphere) -> Self {
sphere.mesh().build()
}
}

View File

@@ -0,0 +1,66 @@
use super::triangle3d;
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::primitives::{Tetrahedron, Triangle3d};
use bevy_reflect::prelude::*;
/// A builder used for creating a [`Mesh`] with a [`Tetrahedron`] shape.
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct TetrahedronMeshBuilder {
tetrahedron: Tetrahedron,
}
impl MeshBuilder for TetrahedronMeshBuilder {
fn build(&self) -> Mesh {
let mut faces: Vec<_> = self.tetrahedron.faces().into();
// If the tetrahedron has negative orientation, reverse all the triangles so that
// they still face outward.
if self.tetrahedron.signed_volume().is_sign_negative() {
faces.iter_mut().for_each(Triangle3d::reverse);
}
let mut positions = vec![];
let mut normals = vec![];
let mut uvs = vec![];
// Each face is meshed as a `Triangle3d`, and we just shove the data into the
// vertex attributes sequentially.
for face in faces {
positions.extend(face.vertices);
let face_normal = triangle3d::normal_vec(&face);
normals.extend(vec![face_normal; 3]);
let face_uvs = triangle3d::uv_coords(&face);
uvs.extend(face_uvs);
}
// There are four faces and none of them share vertices.
let indices = Indices::U32(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(indices)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl Meshable for Tetrahedron {
type Output = TetrahedronMeshBuilder;
fn mesh(&self) -> Self::Output {
TetrahedronMeshBuilder { tetrahedron: *self }
}
}
impl From<Tetrahedron> for Mesh {
fn from(tetrahedron: Tetrahedron) -> Self {
tetrahedron.mesh().build()
}
}

View File

@@ -0,0 +1,176 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{ops, primitives::Torus, Vec3};
use bevy_reflect::prelude::*;
use core::ops::RangeInclusive;
/// A builder used for creating a [`Mesh`] with a [`Torus`] shape.
#[derive(Clone, Debug, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct TorusMeshBuilder {
/// The [`Torus`] shape.
pub torus: Torus,
/// The number of vertices used for each circular segment
/// in the ring or tube of the torus.
///
/// The default is `24`.
pub minor_resolution: usize,
/// The number of segments used for the main ring of the torus.
///
/// A resolution of `4` would make the torus appear rectangular,
/// while a resolution of `32` resembles a circular ring.
///
/// The default is `32`.
pub major_resolution: usize,
/// Optional angle range in radians, defaults to a full circle (0.0..=2 * PI)
pub angle_range: RangeInclusive<f32>,
}
impl Default for TorusMeshBuilder {
fn default() -> Self {
Self {
torus: Torus::default(),
minor_resolution: 24,
major_resolution: 32,
angle_range: (0.0..=2.0 * core::f32::consts::PI),
}
}
}
impl TorusMeshBuilder {
/// Creates a new [`TorusMeshBuilder`] from an inner and outer radius.
///
/// The inner radius is the radius of the hole, and the outer radius
/// is the radius of the entire object.
#[inline]
pub fn new(inner_radius: f32, outer_radius: f32) -> Self {
Self {
torus: Torus::new(inner_radius, outer_radius),
..Default::default()
}
}
/// Sets the number of vertices used for each circular segment
/// in the ring or tube of the torus.
#[inline]
pub const fn minor_resolution(mut self, resolution: usize) -> Self {
self.minor_resolution = resolution;
self
}
/// Sets the number of segments used for the main ring of the torus.
///
/// A resolution of `4` would make the torus appear rectangular,
/// while a resolution of `32` resembles a circular ring.
#[inline]
pub const fn major_resolution(mut self, resolution: usize) -> Self {
self.major_resolution = resolution;
self
}
/// Sets a custom angle range in radians instead of a full circle
#[inline]
pub const fn angle_range(mut self, range: RangeInclusive<f32>) -> Self {
self.angle_range = range;
self
}
}
impl MeshBuilder for TorusMeshBuilder {
fn build(&self) -> Mesh {
// code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html
let n_vertices = (self.major_resolution + 1) * (self.minor_resolution + 1);
let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
let start_angle = self.angle_range.start();
let end_angle = self.angle_range.end();
let segment_stride = (end_angle - start_angle) / self.major_resolution as f32;
let side_stride = 2.0 * core::f32::consts::PI / self.minor_resolution as f32;
for segment in 0..=self.major_resolution {
let theta = start_angle + segment_stride * segment as f32;
for side in 0..=self.minor_resolution {
let phi = side_stride * side as f32;
let (sin_theta, cos_theta) = ops::sin_cos(theta);
let (sin_phi, cos_phi) = ops::sin_cos(phi);
let radius = self.torus.major_radius + self.torus.minor_radius * cos_phi;
let position = Vec3::new(
cos_theta * radius,
self.torus.minor_radius * sin_phi,
sin_theta * radius,
);
let center = Vec3::new(
self.torus.major_radius * cos_theta,
0.,
self.torus.major_radius * sin_theta,
);
let normal = (position - center).normalize();
positions.push(position.into());
normals.push(normal.into());
uvs.push([
segment as f32 / self.major_resolution as f32,
side as f32 / self.minor_resolution as f32,
]);
}
}
let n_faces = (self.major_resolution) * (self.minor_resolution);
let n_triangles = n_faces * 2;
let n_indices = n_triangles * 3;
let mut indices: Vec<u32> = Vec::with_capacity(n_indices);
let n_vertices_per_row = self.minor_resolution + 1;
for segment in 0..self.major_resolution {
for side in 0..self.minor_resolution {
let lt = side + segment * n_vertices_per_row;
let rt = (side + 1) + segment * n_vertices_per_row;
let lb = side + (segment + 1) * n_vertices_per_row;
let rb = (side + 1) + (segment + 1) * n_vertices_per_row;
indices.push(lt as u32);
indices.push(rt as u32);
indices.push(lb as u32);
indices.push(rt as u32);
indices.push(rb as u32);
indices.push(lb as u32);
}
}
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl Meshable for Torus {
type Output = TorusMeshBuilder;
fn mesh(&self) -> Self::Output {
TorusMeshBuilder {
torus: *self,
..Default::default()
}
}
}
impl From<Torus> for Mesh {
fn from(torus: Torus) -> Self {
torus.mesh().build()
}
}

View File

@@ -0,0 +1,130 @@
use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology};
use bevy_asset::RenderAssetUsages;
use bevy_math::{primitives::Triangle3d, Vec3};
use bevy_reflect::prelude::*;
/// A builder used for creating a [`Mesh`] with a [`Triangle3d`] shape.
#[derive(Clone, Copy, Debug, Default, Reflect)]
#[reflect(Default, Debug, Clone)]
pub struct Triangle3dMeshBuilder {
triangle: Triangle3d,
}
impl MeshBuilder for Triangle3dMeshBuilder {
fn build(&self) -> Mesh {
let positions: Vec<_> = self.triangle.vertices.into();
let uvs: Vec<_> = uv_coords(&self.triangle).into();
// Every vertex has the normal of the face of the triangle (or zero if the triangle is degenerate).
let normal: Vec3 = normal_vec(&self.triangle);
let normals = vec![normal; 3];
let indices = Indices::U32(vec![0, 1, 2]);
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_indices(indices)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
impl Meshable for Triangle3d {
type Output = Triangle3dMeshBuilder;
fn mesh(&self) -> Self::Output {
Triangle3dMeshBuilder { triangle: *self }
}
}
/// The normal of a [`Triangle3d`] with zeroing so that a [`Vec3`] is always obtained for meshing.
#[inline]
pub(crate) fn normal_vec(triangle: &Triangle3d) -> Vec3 {
triangle.normal().map_or(Vec3::ZERO, Into::into)
}
/// Unskewed uv-coordinates for a [`Triangle3d`].
#[inline]
pub(crate) fn uv_coords(triangle: &Triangle3d) -> [[f32; 2]; 3] {
let [a, b, c] = triangle.vertices;
let main_length = a.distance(b);
let Some(x) = (b - a).try_normalize() else {
return [[0., 0.], [1., 0.], [0., 1.]];
};
let y = c - a;
// `x` corresponds to one of the axes in uv-coordinates;
// to uv-map the triangle without skewing, we use the orthogonalization
// of `y` with respect to `x` as the second direction and construct a rectangle that
// contains `triangle`.
let y_proj = y.project_onto_normalized(x);
// `offset` represents the x-coordinate of the point `c`; note that x has been shrunk by a
// factor of `main_length`, so `offset` follows it.
let offset = y_proj.dot(x) / main_length;
// Obtuse triangle leaning to the left => x direction extends to the left, shifting a from 0.
if offset < 0. {
let total_length = 1. - offset;
let a_uv = [offset.abs() / total_length, 0.];
let b_uv = [1., 0.];
let c_uv = [0., 1.];
[a_uv, b_uv, c_uv]
}
// Obtuse triangle leaning to the right => x direction extends to the right, shifting b from 1.
else if offset > 1. {
let a_uv = [0., 0.];
let b_uv = [1. / offset, 0.];
let c_uv = [1., 1.];
[a_uv, b_uv, c_uv]
}
// Acute triangle => no extending necessary; a remains at 0 and b remains at 1.
else {
let a_uv = [0., 0.];
let b_uv = [1., 0.];
let c_uv = [offset, 1.];
[a_uv, b_uv, c_uv]
}
}
impl From<Triangle3d> for Mesh {
fn from(triangle: Triangle3d) -> Self {
triangle.mesh().build()
}
}
#[cfg(test)]
mod tests {
use super::uv_coords;
use bevy_math::primitives::Triangle3d;
#[test]
fn uv_test() {
use bevy_math::vec3;
let mut triangle = Triangle3d::new(vec3(0., 0., 0.), vec3(2., 0., 0.), vec3(-1., 1., 0.));
let [a_uv, b_uv, c_uv] = uv_coords(&triangle);
assert_eq!(a_uv, [1. / 3., 0.]);
assert_eq!(b_uv, [1., 0.]);
assert_eq!(c_uv, [0., 1.]);
triangle.vertices[2] = vec3(3., 1., 0.);
let [a_uv, b_uv, c_uv] = uv_coords(&triangle);
assert_eq!(a_uv, [0., 0.]);
assert_eq!(b_uv, [2. / 3., 0.]);
assert_eq!(c_uv, [1., 1.]);
triangle.vertices[2] = vec3(2., 1., 0.);
let [a_uv, b_uv, c_uv] = uv_coords(&triangle);
assert_eq!(a_uv, [0., 0.]);
assert_eq!(b_uv, [1., 0.]);
assert_eq!(c_uv, [1., 1.]);
}
}

View File

@@ -0,0 +1,425 @@
use bevy_math::{
primitives::{Annulus, Capsule2d, Circle, Ellipse, Extrusion, Primitive2d},
Vec2, Vec3,
};
use super::{MeshBuilder, Meshable};
use crate::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
/// A type representing a segment of the perimeter of an extrudable mesh.
pub enum PerimeterSegment {
/// This segment of the perimeter will be shaded smooth.
///
/// This has the effect of rendering the segment's faces with softened edges, so it is appropriate for curved shapes.
///
/// The normals for the vertices that are part of this segment will be calculated based on the positions of their neighbors.
/// Each normal is interpolated between the normals of the two line segments connecting it with its neighbors.
/// Closer vertices have a stronger effect on the normal than more distant ones.
///
/// Since the vertices corresponding to the first and last indices do not have two neighboring vertices, their normals must be provided manually.
Smooth {
/// The normal of the first vertex.
first_normal: Vec2,
/// The normal of the last vertex.
last_normal: Vec2,
/// A list of indices representing this segment of the perimeter of the mesh.
///
/// The indices must be ordered such that the *outside* of the mesh is to the right
/// when walking along the vertices of the mesh in the order provided by the indices.
///
/// For geometry to be rendered, you must provide at least two indices.
indices: Vec<u32>,
},
/// This segment of the perimeter will be shaded flat.
///
/// This has the effect of rendering the segment's faces with hard edges.
Flat {
/// A list of indices representing this segment of the perimeter of the mesh.
///
/// The indices must be ordered such that the *outside* of the mesh is to the right
/// when walking along the vertices of the mesh in the order provided by indices.
///
/// For geometry to be rendered, you must provide at least two indices.
indices: Vec<u32>,
},
}
impl PerimeterSegment {
/// Returns the amount of vertices each 'layer' of the extrusion should include for this perimeter segment.
///
/// A layer is the set of vertices sharing a common Z value or depth.
fn vertices_per_layer(&self) -> u32 {
match self {
PerimeterSegment::Smooth { indices, .. } => indices.len() as u32,
PerimeterSegment::Flat { indices } => 2 * (indices.len() as u32 - 1),
}
}
/// Returns the amount of indices each 'segment' of the extrusion should include for this perimeter segment.
///
/// A segment is the set of faces on the mantel of the extrusion between two layers of vertices.
fn indices_per_segment(&self) -> usize {
match self {
PerimeterSegment::Smooth { indices, .. } | PerimeterSegment::Flat { indices } => {
6 * (indices.len() - 1)
}
}
}
}
/// A trait required for implementing `Meshable` for `Extrusion<T>`.
///
/// ## Warning
///
/// By implementing this trait you guarantee that the `primitive_topology` of the mesh returned by
/// this builder is [`PrimitiveTopology::TriangleList`]
/// and that your mesh has a [`Mesh::ATTRIBUTE_POSITION`] attribute.
pub trait Extrudable: MeshBuilder {
/// A list of the indices each representing a part of the perimeter of the mesh.
fn perimeter(&self) -> Vec<PerimeterSegment>;
}
impl<P> Meshable for Extrusion<P>
where
P: Primitive2d + Meshable,
P::Output: Extrudable,
{
type Output = ExtrusionBuilder<P>;
fn mesh(&self) -> Self::Output {
ExtrusionBuilder {
base_builder: self.base_shape.mesh(),
half_depth: self.half_depth,
segments: 1,
}
}
}
/// A builder used for creating a [`Mesh`] with an [`Extrusion`] shape.
pub struct ExtrusionBuilder<P>
where
P: Primitive2d + Meshable,
P::Output: Extrudable,
{
pub base_builder: P::Output,
pub half_depth: f32,
pub segments: usize,
}
impl<P> ExtrusionBuilder<P>
where
P: Primitive2d + Meshable,
P::Output: Extrudable,
{
/// Create a new `ExtrusionBuilder<P>` from a given `base_shape` and the full `depth` of the extrusion.
pub fn new(base_shape: &P, depth: f32) -> Self {
Self {
base_builder: base_shape.mesh(),
half_depth: depth / 2.,
segments: 1,
}
}
/// Sets the number of segments along the depth of the extrusion.
/// Must be greater than `0` for the geometry of the mantel to be generated.
pub fn segments(mut self, segments: usize) -> Self {
self.segments = segments;
self
}
}
impl ExtrusionBuilder<Circle> {
/// Sets the number of vertices used for the circle mesh at each end of the extrusion.
pub fn resolution(mut self, resolution: u32) -> Self {
self.base_builder.resolution = resolution;
self
}
}
impl ExtrusionBuilder<Ellipse> {
/// Sets the number of vertices used for the ellipse mesh at each end of the extrusion.
pub fn resolution(mut self, resolution: u32) -> Self {
self.base_builder.resolution = resolution;
self
}
}
impl ExtrusionBuilder<Annulus> {
/// Sets the number of vertices used in constructing the concentric circles of the annulus mesh at each end of the extrusion.
pub fn resolution(mut self, resolution: u32) -> Self {
self.base_builder.resolution = resolution;
self
}
}
impl ExtrusionBuilder<Capsule2d> {
/// Sets the number of vertices used for each hemicircle at the ends of the extrusion.
pub fn resolution(mut self, resolution: u32) -> Self {
self.base_builder.resolution = resolution;
self
}
}
impl<P> MeshBuilder for ExtrusionBuilder<P>
where
P: Primitive2d + Meshable,
P::Output: Extrudable,
{
fn build(&self) -> Mesh {
// Create and move the base mesh to the front
let mut front_face =
self.base_builder
.build()
.translated_by(Vec3::new(0., 0., self.half_depth));
// Move the uvs of the front face to be between (0., 0.) and (0.5, 0.5)
if let Some(VertexAttributeValues::Float32x2(uvs)) =
front_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)
{
for uv in uvs {
*uv = uv.map(|coord| coord * 0.5);
}
}
let back_face = {
let topology = front_face.primitive_topology();
// Flip the normals, etc. and move mesh to the back
let mut back_face = front_face.clone().scaled_by(Vec3::new(1., 1., -1.));
// Move the uvs of the back face to be between (0.5, 0.) and (1., 0.5)
if let Some(VertexAttributeValues::Float32x2(uvs)) =
back_face.attribute_mut(Mesh::ATTRIBUTE_UV_0)
{
for uv in uvs {
*uv = [uv[0] + 0.5, uv[1]];
}
}
// By swapping the first and second indices of each triangle we invert the winding order thus making the mesh visible from the other side
if let Some(indices) = back_face.indices_mut() {
match topology {
PrimitiveTopology::TriangleList => match indices {
Indices::U16(indices) => {
indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));
}
Indices::U32(indices) => {
indices.chunks_exact_mut(3).for_each(|arr| arr.swap(1, 0));
}
},
_ => {
panic!("Meshes used with Extrusions must have a primitive topology of `PrimitiveTopology::TriangleList`");
}
};
}
back_face
};
// An extrusion of depth 0 does not need a mantel
if self.half_depth == 0. {
front_face.merge(&back_face).unwrap();
return front_face;
}
let mantel = {
let Some(VertexAttributeValues::Float32x3(cap_verts)) =
front_face.attribute(Mesh::ATTRIBUTE_POSITION)
else {
panic!("The base mesh did not have vertex positions");
};
debug_assert!(self.segments > 0);
let layers = self.segments + 1;
let layer_depth_delta = self.half_depth * 2.0 / self.segments as f32;
let perimeter = self.base_builder.perimeter();
let (vert_count, index_count) =
perimeter
.iter()
.fold((0, 0), |(verts, indices), perimeter| {
(
verts + layers * perimeter.vertices_per_layer() as usize,
indices + self.segments * perimeter.indices_per_segment(),
)
});
let mut positions = Vec::with_capacity(vert_count);
let mut normals = Vec::with_capacity(vert_count);
let mut indices = Vec::with_capacity(index_count);
let mut uvs = Vec::with_capacity(vert_count);
// Compute the amount of horizontal space allocated to each segment of the perimeter.
let uv_segment_delta = 1. / perimeter.len() as f32;
for (i, segment) in perimeter.into_iter().enumerate() {
// The start of the x range of the area of the current perimeter-segment.
let uv_start = i as f32 * uv_segment_delta;
match segment {
PerimeterSegment::Flat {
indices: segment_indices,
} => {
let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
for i in 0..(segment_indices.len() - 1) {
let uv_x = uv_start + uv_delta * i as f32;
// Get the positions for the current and the next index.
let a = cap_verts[segment_indices[i] as usize];
let b = cap_verts[segment_indices[i + 1] as usize];
// Get the index of the next vertex added to the mantel.
let index = positions.len() as u32;
// Push the positions of the two indices and their equivalent points on each layer.
for i in 0..layers {
let i = i as f32;
let z = a[2] - layer_depth_delta * i;
positions.push([a[0], a[1], z]);
positions.push([b[0], b[1], z]);
// UVs for the mantel are between (0, 0.5) and (1, 1).
let uv_y = 0.5 + 0.5 * i / self.segments as f32;
uvs.push([uv_x, uv_y]);
uvs.push([uv_x + uv_delta, uv_y]);
}
// The normal is calculated to be the normal of the line segment connecting a and b.
let n = Vec3::from_array([b[1] - a[1], a[0] - b[0], 0.])
.normalize_or_zero()
.to_array();
normals.extend_from_slice(&vec![n; 2 * layers]);
// Add the indices for the vertices created above to the mesh.
for i in 0..self.segments as u32 {
let base_index = index + 2 * i;
indices.extend_from_slice(&[
base_index,
base_index + 2,
base_index + 1,
base_index + 1,
base_index + 2,
base_index + 3,
]);
}
}
}
PerimeterSegment::Smooth {
first_normal,
last_normal,
indices: segment_indices,
} => {
let uv_delta = uv_segment_delta / (segment_indices.len() - 1) as f32;
// Since the indices for this segment will be added after its vertices have been added,
// we need to store the index of the first vertex that is part of this segment.
let base_index = positions.len() as u32;
// If there is a first vertex, we need to add it and its counterparts on each layer.
// The normal is provided by `segment.first_normal`.
if let Some(i) = segment_indices.first() {
let p = cap_verts[*i as usize];
for i in 0..layers {
let i = i as f32;
let z = p[2] - layer_depth_delta * i;
positions.push([p[0], p[1], z]);
let uv_y = 0.5 + 0.5 * i / self.segments as f32;
uvs.push([uv_start, uv_y]);
}
normals.extend_from_slice(&vec![
first_normal.extend(0.).to_array();
layers
]);
}
// For all points inbetween the first and last vertices, we can automatically compute the normals.
for i in 1..(segment_indices.len() - 1) {
let uv_x = uv_start + uv_delta * i as f32;
// Get the positions for the last, current and the next index.
let a = cap_verts[segment_indices[i - 1] as usize];
let b = cap_verts[segment_indices[i] as usize];
let c = cap_verts[segment_indices[i + 1] as usize];
// Add the current vertex and its counterparts on each layer.
for i in 0..layers {
let i = i as f32;
let z = b[2] - layer_depth_delta * i;
positions.push([b[0], b[1], z]);
let uv_y = 0.5 + 0.5 * i / self.segments as f32;
uvs.push([uv_x, uv_y]);
}
// The normal for the current vertices can be calculated based on the two neighboring vertices.
// The normal is interpolated between the normals of the two line segments connecting the current vertex with its neighbors.
// Closer vertices have a stronger effect on the normal than more distant ones.
let n = {
let ab = Vec2::from_slice(&b) - Vec2::from_slice(&a);
let bc = Vec2::from_slice(&c) - Vec2::from_slice(&b);
let n = ab.normalize_or_zero() + bc.normalize_or_zero();
Vec2::new(n.y, -n.x)
.normalize_or_zero()
.extend(0.)
.to_array()
};
normals.extend_from_slice(&vec![n; layers]);
}
// If there is a last vertex, we need to add it and its counterparts on each layer.
// The normal is provided by `segment.last_normal`.
if let Some(i) = segment_indices.last() {
let p = cap_verts[*i as usize];
for i in 0..layers {
let i = i as f32;
let z = p[2] - layer_depth_delta * i;
positions.push([p[0], p[1], z]);
let uv_y = 0.5 + 0.5 * i / self.segments as f32;
uvs.push([uv_start + uv_segment_delta, uv_y]);
}
normals.extend_from_slice(&vec![
last_normal.extend(0.).to_array();
layers
]);
}
let columns = segment_indices.len() as u32;
let segments = self.segments as u32;
let layers = segments + 1;
for s in 0..segments {
for column in 0..(columns - 1) {
let index = base_index + s + column * layers;
indices.extend_from_slice(&[
index,
index + 1,
index + layers,
index + layers,
index + 1,
index + layers + 1,
]);
}
}
}
}
}
Mesh::new(PrimitiveTopology::TriangleList, front_face.asset_usage)
.with_inserted_indices(Indices::U32(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
};
front_face.merge(&back_face).unwrap();
front_face.merge(&mantel).unwrap();
front_face
}
}
impl<P> From<Extrusion<P>> for Mesh
where
P: Primitive2d + Meshable,
P::Output: Extrudable,
{
fn from(value: Extrusion<P>) -> Self {
value.mesh().build()
}
}

52
vendor/bevy_mesh/src/primitives/mod.rs vendored Normal file
View File

@@ -0,0 +1,52 @@
//! Mesh generation for [primitive shapes](bevy_math::primitives).
//!
//! Primitives that support meshing implement the [`Meshable`] trait.
//! Calling [`mesh`](Meshable::mesh) will return either a [`Mesh`] or a builder
//! that can be used to specify shape-specific configuration for creating the [`Mesh`].
//!
//! ```
//! # use bevy_asset::Assets;
//! # use bevy_ecs::prelude::ResMut;
//! # use bevy_math::prelude::Circle;
//! # use bevy_mesh::*;
//! #
//! # fn setup(mut meshes: ResMut<Assets<Mesh>>) {
//! // Create circle mesh with default configuration
//! let circle = meshes.add(Circle { radius: 25.0 });
//!
//! // Specify number of vertices
//! let circle = meshes.add(Circle { radius: 25.0 }.mesh().resolution(64));
//! # }
//! ```
mod dim2;
pub use dim2::*;
mod dim3;
pub use dim3::*;
mod extrusion;
pub use extrusion::*;
use super::Mesh;
/// A trait for shapes that can be turned into a [`Mesh`].
pub trait Meshable {
/// The output of [`Self::mesh`]. This will be a [`MeshBuilder`] used for creating a [`Mesh`].
type Output: MeshBuilder;
/// Creates a [`Mesh`] for a shape.
fn mesh(&self) -> Self::Output;
}
/// A trait used to build [`Mesh`]es from a configuration
pub trait MeshBuilder {
/// Builds a [`Mesh`] based on the configuration in `self`.
fn build(&self) -> Mesh;
}
impl<T: MeshBuilder> From<T> for Mesh {
fn from(builder: T) -> Self {
builder.build()
}
}