use std::f32::consts::FRAC_PI_2;
use crate::{
mesh::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment},
render_asset::RenderAssetUsages,
};
use super::{Extrudable, MeshBuilder, Meshable};
use bevy_math::{
primitives::{
Annulus, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Rectangle,
RegularPolygon, Rhombus, Triangle2d, Triangle3d, WindingOrder,
},
FloatExt, Vec2,
};
use wgpu::PrimitiveTopology;
#[derive(Clone, Copy, Debug)]
pub struct CircleMeshBuilder {
pub circle: Circle,
#[doc(alias = "vertices")]
pub resolution: usize,
}
impl Default for CircleMeshBuilder {
fn default() -> Self {
Self {
circle: Circle::default(),
resolution: 32,
}
}
}
impl CircleMeshBuilder {
#[inline]
pub const fn new(radius: f32, resolution: usize) -> Self {
Self {
circle: Circle { radius },
resolution,
}
}
#[inline]
#[doc(alias = "vertices")]
pub const fn resolution(mut self, resolution: usize) -> Self {
self.resolution = resolution;
self
}
}
impl MeshBuilder for CircleMeshBuilder {
fn build(&self) -> Mesh {
RegularPolygon::new(self.circle.radius, self.resolution)
.mesh()
.build()
}
}
impl Extrudable for CircleMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
vec![PerimeterSegment::Smooth {
first_normal: Vec2::Y,
last_normal: Vec2::Y,
indices: (0..self.resolution as u32).chain([0]).collect(),
}]
}
}
impl Meshable for Circle {
type Output = CircleMeshBuilder;
fn mesh(&self) -> Self::Output {
CircleMeshBuilder {
circle: *self,
..Default::default()
}
}
}
impl From<Circle> for Mesh {
fn from(circle: Circle) -> Self {
circle.mesh().build()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum CircularMeshUvMode {
Mask {
angle: f32,
},
}
impl Default for CircularMeshUvMode {
fn default() -> Self {
CircularMeshUvMode::Mask { angle: 0.0 }
}
}
#[derive(Clone, Debug)]
pub struct CircularSectorMeshBuilder {
pub sector: CircularSector,
#[doc(alias = "vertices")]
pub resolution: usize,
pub uv_mode: CircularMeshUvMode,
}
impl Default for CircularSectorMeshBuilder {
fn default() -> Self {
Self {
sector: CircularSector::default(),
resolution: 32,
uv_mode: CircularMeshUvMode::default(),
}
}
}
impl CircularSectorMeshBuilder {
#[inline]
pub fn new(sector: CircularSector) -> Self {
Self {
sector,
..Self::default()
}
}
#[inline]
#[doc(alias = "vertices")]
pub const fn resolution(mut self, resolution: usize) -> Self {
self.resolution = resolution;
self
}
#[inline]
pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
self.uv_mode = uv_mode;
self
}
}
impl MeshBuilder for CircularSectorMeshBuilder {
fn build(&self) -> Mesh {
let mut indices = Vec::with_capacity((self.resolution - 1) * 3);
let mut positions = Vec::with_capacity(self.resolution + 1);
let normals = vec![[0.0, 0.0, 1.0]; self.resolution + 1];
let mut uvs = Vec::with_capacity(self.resolution + 1);
let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
positions.push([0.0; 3]);
uvs.push([0.5; 2]);
let first_angle = FRAC_PI_2 - self.sector.half_angle();
let last_angle = FRAC_PI_2 + self.sector.half_angle();
let last_i = (self.resolution - 1) as f32;
for i in 0..self.resolution {
let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
let vertex = self.sector.radius() * Vec2::from_angle(angle);
let uv =
Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
positions.push([vertex.x, vertex.y, 0.0]);
uvs.push([uv.x, uv.y]);
}
for i in 1..(self.resolution as u32) {
indices.extend_from_slice(&[0, i, i + 1]);
}
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::U32(indices))
}
}
impl Extrudable for CircularSectorMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
let resolution = self.resolution as u32;
let (sin, cos) = self.sector.arc.half_angle.sin_cos();
let first_normal = Vec2::new(sin, cos);
let last_normal = Vec2::new(-sin, cos);
vec![
PerimeterSegment::Flat {
indices: vec![resolution, 0, 1],
},
PerimeterSegment::Smooth {
first_normal,
last_normal,
indices: (1..=resolution).collect(),
},
]
}
}
impl Meshable for CircularSector {
type Output = CircularSectorMeshBuilder;
fn mesh(&self) -> Self::Output {
CircularSectorMeshBuilder {
sector: *self,
..Default::default()
}
}
}
impl From<CircularSector> for Mesh {
fn from(sector: CircularSector) -> Self {
sector.mesh().build()
}
}
#[derive(Clone, Copy, Debug)]
pub struct CircularSegmentMeshBuilder {
pub segment: CircularSegment,
#[doc(alias = "vertices")]
pub resolution: usize,
pub uv_mode: CircularMeshUvMode,
}
impl Default for CircularSegmentMeshBuilder {
fn default() -> Self {
Self {
segment: CircularSegment::default(),
resolution: 32,
uv_mode: CircularMeshUvMode::default(),
}
}
}
impl CircularSegmentMeshBuilder {
#[inline]
pub fn new(segment: CircularSegment) -> Self {
Self {
segment,
..Self::default()
}
}
#[inline]
#[doc(alias = "vertices")]
pub const fn resolution(mut self, resolution: usize) -> Self {
self.resolution = resolution;
self
}
#[inline]
pub const fn uv_mode(mut self, uv_mode: CircularMeshUvMode) -> Self {
self.uv_mode = uv_mode;
self
}
}
impl MeshBuilder for CircularSegmentMeshBuilder {
fn build(&self) -> Mesh {
let mut indices = Vec::with_capacity((self.resolution - 1) * 3);
let mut positions = Vec::with_capacity(self.resolution + 1);
let normals = vec![[0.0, 0.0, 1.0]; self.resolution + 1];
let mut uvs = Vec::with_capacity(self.resolution + 1);
let CircularMeshUvMode::Mask { angle: uv_angle } = self.uv_mode;
let midpoint_vertex = self.segment.chord_midpoint();
positions.push([midpoint_vertex.x, midpoint_vertex.y, 0.0]);
let midpoint_uv = Vec2::from_angle(-uv_angle - FRAC_PI_2).mul_add(
Vec2::splat(0.5 * (self.segment.apothem() / self.segment.radius())),
Vec2::splat(0.5),
);
uvs.push([midpoint_uv.x, midpoint_uv.y]);
let first_angle = FRAC_PI_2 - self.segment.half_angle();
let last_angle = FRAC_PI_2 + self.segment.half_angle();
let last_i = (self.resolution - 1) as f32;
for i in 0..self.resolution {
let angle = f32::lerp(first_angle, last_angle, i as f32 / last_i);
let vertex = self.segment.radius() * Vec2::from_angle(angle);
let uv =
Vec2::from_angle(-(angle + uv_angle)).mul_add(Vec2::splat(0.5), Vec2::splat(0.5));
positions.push([vertex.x, vertex.y, 0.0]);
uvs.push([uv.x, uv.y]);
}
for i in 1..(self.resolution as u32) {
indices.extend_from_slice(&[0, i, i + 1]);
}
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::U32(indices))
}
}
impl Extrudable for CircularSegmentMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
let resolution = self.resolution as u32;
let (sin, cos) = self.segment.arc.half_angle.sin_cos();
let first_normal = Vec2::new(sin, cos);
let last_normal = Vec2::new(-sin, cos);
vec![
PerimeterSegment::Flat {
indices: vec![resolution, 0, 1],
},
PerimeterSegment::Smooth {
first_normal,
last_normal,
indices: (1..=resolution).collect(),
},
]
}
}
impl Meshable for CircularSegment {
type Output = CircularSegmentMeshBuilder;
fn mesh(&self) -> Self::Output {
CircularSegmentMeshBuilder {
segment: *self,
..Default::default()
}
}
}
impl From<CircularSegment> for Mesh {
fn from(segment: CircularSegment) -> Self {
segment.mesh().build()
}
}
pub struct RegularPolygonMeshBuilder {
circumradius: f32,
sides: usize,
}
impl Meshable for RegularPolygon {
type Output = RegularPolygonMeshBuilder;
fn mesh(&self) -> Self::Output {
Self::Output {
circumradius: self.circumcircle.radius,
sides: self.sides,
}
}
}
impl MeshBuilder for RegularPolygonMeshBuilder {
fn build(&self) -> Mesh {
Ellipse::new(self.circumradius, self.circumradius)
.mesh()
.resolution(self.sides)
.build()
}
}
impl Extrudable for RegularPolygonMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
vec![PerimeterSegment::Flat {
indices: (0..self.sides as u32).chain([0]).collect(),
}]
}
}
impl From<RegularPolygon> for Mesh {
fn from(polygon: RegularPolygon) -> Self {
polygon.mesh().build()
}
}
#[derive(Clone, Copy, Debug)]
pub struct EllipseMeshBuilder {
pub ellipse: Ellipse,
#[doc(alias = "vertices")]
pub resolution: usize,
}
impl Default for EllipseMeshBuilder {
fn default() -> Self {
Self {
ellipse: Ellipse::default(),
resolution: 32,
}
}
}
impl EllipseMeshBuilder {
#[inline]
pub const fn new(half_width: f32, half_height: f32, resolution: usize) -> Self {
Self {
ellipse: Ellipse::new(half_width, half_height),
resolution,
}
}
#[inline]
#[doc(alias = "vertices")]
pub const fn resolution(mut self, resolution: usize) -> Self {
self.resolution = resolution;
self
}
}
impl MeshBuilder for EllipseMeshBuilder {
fn build(&self) -> Mesh {
let mut indices = Vec::with_capacity((self.resolution - 2) * 3);
let mut positions = Vec::with_capacity(self.resolution);
let normals = vec![[0.0, 0.0, 1.0]; self.resolution];
let mut uvs = Vec::with_capacity(self.resolution);
let start_angle = std::f32::consts::FRAC_PI_2;
let step = std::f32::consts::TAU / self.resolution as f32;
for i in 0..self.resolution {
let theta = start_angle + i as f32 * step;
let (sin, cos) = theta.sin_cos();
let x = cos * self.ellipse.half_size.x;
let y = sin * self.ellipse.half_size.y;
positions.push([x, y, 0.0]);
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
}
for i in 1..(self.resolution as u32 - 1) {
indices.extend_from_slice(&[0, i, i + 1]);
}
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::U32(indices))
}
}
impl Extrudable for EllipseMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
vec![PerimeterSegment::Smooth {
first_normal: Vec2::Y,
last_normal: Vec2::Y,
indices: (0..self.resolution as u32).chain([0]).collect(),
}]
}
}
impl Meshable for Ellipse {
type Output = EllipseMeshBuilder;
fn mesh(&self) -> Self::Output {
EllipseMeshBuilder {
ellipse: *self,
..Default::default()
}
}
}
impl From<Ellipse> for Mesh {
fn from(ellipse: Ellipse) -> Self {
ellipse.mesh().build()
}
}
pub struct AnnulusMeshBuilder {
pub annulus: Annulus,
pub resolution: usize,
}
impl Default for AnnulusMeshBuilder {
fn default() -> Self {
Self {
annulus: Annulus::default(),
resolution: 32,
}
}
}
impl AnnulusMeshBuilder {
#[inline]
pub fn new(inner_radius: f32, outer_radius: f32, resolution: usize) -> Self {
Self {
annulus: Annulus::new(inner_radius, outer_radius),
resolution,
}
}
#[inline]
pub fn resolution(mut self, resolution: usize) -> Self {
self.resolution = resolution;
self
}
}
impl MeshBuilder for AnnulusMeshBuilder {
fn build(&self) -> Mesh {
let inner_radius = self.annulus.inner_circle.radius;
let outer_radius = self.annulus.outer_circle.radius;
let num_vertices = (self.resolution + 1) * 2;
let mut indices = Vec::with_capacity(self.resolution * 6);
let mut positions = Vec::with_capacity(num_vertices);
let mut uvs = Vec::with_capacity(num_vertices);
let normals = vec![[0.0, 0.0, 1.0]; num_vertices];
let start_angle = std::f32::consts::FRAC_PI_2;
let step = std::f32::consts::TAU / self.resolution as f32;
for i in 0..=self.resolution {
let theta = start_angle + i as f32 * step;
let (sin, cos) = theta.sin_cos();
let inner_pos = [cos * inner_radius, sin * inner_radius, 0.];
let outer_pos = [cos * outer_radius, sin * outer_radius, 0.];
positions.push(inner_pos);
positions.push(outer_pos);
let inner_uv = [0., i as f32 / self.resolution as f32];
let outer_uv = [1., i as f32 / self.resolution as f32];
uvs.push(inner_uv);
uvs.push(outer_uv);
}
for i in 0..(self.resolution as u32) {
let inner_vertex = 2 * i;
let outer_vertex = 2 * i + 1;
let next_inner = inner_vertex + 2;
let next_outer = outer_vertex + 2;
indices.extend_from_slice(&[inner_vertex, outer_vertex, next_outer]);
indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]);
}
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::U32(indices))
}
}
impl Extrudable for AnnulusMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
let vert_count = 2 * self.resolution as u32;
vec![
PerimeterSegment::Smooth {
first_normal: Vec2::NEG_Y,
last_normal: Vec2::NEG_Y,
indices: (0..vert_count).step_by(2).chain([0]).rev().collect(), },
PerimeterSegment::Smooth {
first_normal: Vec2::Y,
last_normal: Vec2::Y,
indices: (1..vert_count).step_by(2).chain([1]).collect(), },
]
}
}
impl Meshable for Annulus {
type Output = AnnulusMeshBuilder;
fn mesh(&self) -> Self::Output {
AnnulusMeshBuilder {
annulus: *self,
..Default::default()
}
}
}
impl From<Annulus> for Mesh {
fn from(annulus: Annulus) -> Self {
annulus.mesh().build()
}
}
pub struct RhombusMeshBuilder {
half_diagonals: Vec2,
}
impl MeshBuilder for RhombusMeshBuilder {
fn build(&self) -> Mesh {
let [hhd, vhd] = [self.half_diagonals.x, self.half_diagonals.y];
let positions = vec![
[hhd, 0.0, 0.0],
[-hhd, 0.0, 0.0],
[0.0, vhd, 0.0],
[0.0, -vhd, 0.0],
];
let normals = vec![[0.0, 0.0, 1.0]; 4];
let uvs = vec![[1.0, 0.5], [0.0, 0.5], [0.5, 0.0], [0.5, 1.0]];
let indices = Indices::U32(vec![1, 0, 2, 1, 3, 0]);
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 Extrudable for RhombusMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
vec![PerimeterSegment::Flat {
indices: vec![0, 2, 1, 3, 0],
}]
}
}
impl Meshable for Rhombus {
type Output = RhombusMeshBuilder;
fn mesh(&self) -> Self::Output {
Self::Output {
half_diagonals: self.half_diagonals,
}
}
}
impl From<Rhombus> for Mesh {
fn from(rhombus: Rhombus) -> Self {
rhombus.mesh().build()
}
}
pub struct Triangle2dMeshBuilder {
triangle: Triangle2d,
}
impl Meshable for Triangle2d {
type Output = Triangle2dMeshBuilder;
fn mesh(&self) -> Self::Output {
Self::Output { triangle: *self }
}
}
impl MeshBuilder for Triangle2dMeshBuilder {
fn build(&self) -> Mesh {
let vertices_3d = self.triangle.vertices.map(|v| v.extend(0.));
let positions: Vec<_> = vertices_3d.into();
let normals = vec![[0.0, 0.0, 1.0]; 3];
let uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new(
vertices_3d[0],
vertices_3d[1],
vertices_3d[2],
))
.into();
let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
let indices = if is_ccw {
Indices::U32(vec![0, 1, 2])
} else {
Indices::U32(vec![2, 1, 0])
};
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 Extrudable for Triangle2dMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise;
if is_ccw {
vec![PerimeterSegment::Flat {
indices: vec![0, 1, 2, 0],
}]
} else {
vec![PerimeterSegment::Flat {
indices: vec![2, 1, 0, 2],
}]
}
}
}
impl From<Triangle2d> for Mesh {
fn from(triangle: Triangle2d) -> Self {
triangle.mesh().build()
}
}
pub struct RectangleMeshBuilder {
half_size: Vec2,
}
impl MeshBuilder for RectangleMeshBuilder {
fn build(&self) -> Mesh {
let [hw, hh] = [self.half_size.x, self.half_size.y];
let positions = vec![
[hw, hh, 0.0],
[-hw, hh, 0.0],
[-hw, -hh, 0.0],
[hw, -hh, 0.0],
];
let normals = vec![[0.0, 0.0, 1.0]; 4];
let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]);
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 Extrudable for RectangleMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
vec![PerimeterSegment::Flat {
indices: vec![0, 1, 2, 3, 0],
}]
}
}
impl Meshable for Rectangle {
type Output = RectangleMeshBuilder;
fn mesh(&self) -> Self::Output {
RectangleMeshBuilder {
half_size: self.half_size,
}
}
}
impl From<Rectangle> for Mesh {
fn from(rectangle: Rectangle) -> Self {
rectangle.mesh().build()
}
}
#[derive(Clone, Copy, Debug)]
pub struct Capsule2dMeshBuilder {
pub capsule: Capsule2d,
pub resolution: usize,
}
impl Default for Capsule2dMeshBuilder {
fn default() -> Self {
Self {
capsule: Capsule2d::default(),
resolution: 16,
}
}
}
impl Capsule2dMeshBuilder {
#[inline]
pub fn new(radius: f32, length: f32, resolution: usize) -> Self {
Self {
capsule: Capsule2d::new(radius, length),
resolution,
}
}
#[inline]
pub const fn resolution(mut self, resolution: usize) -> Self {
self.resolution = resolution;
self
}
}
impl MeshBuilder for Capsule2dMeshBuilder {
fn build(&self) -> Mesh {
let resolution = self.resolution as u32;
let vertex_count = 2 * self.resolution;
let mut indices = Vec::with_capacity((self.resolution - 2) * 2 * 3 + 6);
let mut positions = Vec::with_capacity(vertex_count);
let normals = vec![[0.0, 0.0, 1.0]; vertex_count];
let mut uvs = Vec::with_capacity(vertex_count);
let radius = self.capsule.radius;
let step = std::f32::consts::TAU / vertex_count as f32;
let start_angle = if vertex_count % 2 == 0 {
step / 2.0
} else {
0.0
};
let radius_frac = self.capsule.radius / (self.capsule.half_length + self.capsule.radius);
for i in 0..resolution {
let theta = start_angle + i as f32 * step;
let (sin, cos) = theta.sin_cos();
let (x, y) = (cos * radius, sin * radius + self.capsule.half_length);
positions.push([x, y, 0.0]);
uvs.push([0.5 * (cos + 1.0), radius_frac * (1.0 - 0.5 * (sin + 1.0))]);
}
for i in 1..resolution - 1 {
indices.extend_from_slice(&[0, i, i + 1]);
}
indices.extend_from_slice(&[0, resolution - 1, resolution]);
for i in resolution..vertex_count as u32 {
let theta = start_angle + i as f32 * step;
let (sin, cos) = theta.sin_cos();
let (x, y) = (cos * radius, sin * radius - self.capsule.half_length);
positions.push([x, y, 0.0]);
uvs.push([0.5 * (cos + 1.0), 1.0 - radius_frac * 0.5 * (sin + 1.0)]);
}
for i in 1..resolution - 1 {
indices.extend_from_slice(&[resolution, resolution + i, resolution + i + 1]);
}
indices.extend_from_slice(&[resolution, vertex_count as u32 - 1, 0]);
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::U32(indices))
}
}
impl Extrudable for Capsule2dMeshBuilder {
fn perimeter(&self) -> Vec<PerimeterSegment> {
let resolution = self.resolution as u32;
let top_semi_indices = (0..resolution).collect();
let bottom_semi_indices = (resolution..(2 * resolution)).collect();
vec![
PerimeterSegment::Smooth {
first_normal: Vec2::X,
last_normal: Vec2::NEG_X,
indices: top_semi_indices,
}, PerimeterSegment::Flat {
indices: vec![resolution - 1, resolution],
}, PerimeterSegment::Smooth {
first_normal: Vec2::NEG_X,
last_normal: Vec2::X,
indices: bottom_semi_indices,
}, PerimeterSegment::Flat {
indices: vec![2 * resolution - 1, 0],
}, ]
}
}
impl Meshable for Capsule2d {
type Output = Capsule2dMeshBuilder;
fn mesh(&self) -> Self::Output {
Capsule2dMeshBuilder {
capsule: *self,
..Default::default()
}
}
}
impl From<Capsule2d> for Mesh {
fn from(capsule: Capsule2d) -> Self {
capsule.mesh().build()
}
}
#[cfg(test)]
mod tests {
use bevy_math::primitives::RegularPolygon;
use crate::mesh::{Mesh, VertexAttributeValues};
fn fix_floats<const N: usize>(points: &mut [[f32; N]]) {
for point in points.iter_mut() {
for coord in point.iter_mut() {
let round = (*coord * 2.).round() / 2.;
if (*coord - round).abs() < 0.00001 {
*coord = round;
}
}
}
}
#[test]
fn test_regular_polygon() {
let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4));
let Some(VertexAttributeValues::Float32x3(mut positions)) =
mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION)
else {
panic!("Expected positions f32x3");
};
let Some(VertexAttributeValues::Float32x2(mut uvs)) =
mesh.remove_attribute(Mesh::ATTRIBUTE_UV_0)
else {
panic!("Expected uvs f32x2");
};
let Some(VertexAttributeValues::Float32x3(normals)) =
mesh.remove_attribute(Mesh::ATTRIBUTE_NORMAL)
else {
panic!("Expected normals f32x3");
};
fix_floats(&mut positions);
fix_floats(&mut uvs);
assert_eq!(
[
[0.0, 7.0, 0.0],
[-7.0, 0.0, 0.0],
[0.0, -7.0, 0.0],
[7.0, 0.0, 0.0],
],
&positions[..]
);
assert_eq!([[0.5, 0.0], [0.0, 0.5], [0.5, 1.0], [1.0, 0.5],], &uvs[..]);
assert_eq!(&[[0.0, 0.0, 1.0]; 4], &normals[..]);
}
}