use crate::{
mesh::{Indices, Mesh},
render_asset::RenderAssetUsages,
};
use super::Meshable;
use bevy_math::{
primitives::{Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder},
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
}
pub fn build(&self) -> Mesh {
RegularPolygon::new(self.circle.radius, self.resolution).mesh()
}
}
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()
}
}
impl From<CircleMeshBuilder> for Mesh {
fn from(circle: CircleMeshBuilder) -> Self {
circle.build()
}
}
impl Meshable for RegularPolygon {
type Output = Mesh;
fn mesh(&self) -> Self::Output {
Ellipse::new(self.circumcircle.radius, self.circumcircle.radius)
.mesh()
.resolution(self.sides)
.build()
}
}
impl From<RegularPolygon> for Mesh {
fn from(polygon: RegularPolygon) -> Self {
polygon.mesh()
}
}
#[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
}
pub 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 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()
}
}
impl From<EllipseMeshBuilder> for Mesh {
fn from(ellipse: EllipseMeshBuilder) -> Self {
ellipse.build()
}
}
impl Meshable for Triangle2d {
type Output = Mesh;
fn mesh(&self) -> Self::Output {
let [a, b, c] = self.vertices;
let positions = vec![[a.x, a.y, 0.0], [b.x, b.y, 0.0], [c.x, c.y, 0.0]];
let normals = vec![[0.0, 0.0, 1.0]; 3];
let extents = a.min(b).min(c).abs().max(a.max(b).max(c)) * Vec2::new(1.0, -1.0);
let uvs = vec![
a / extents / 2.0 + 0.5,
b / extents / 2.0 + 0.5,
c / extents / 2.0 + 0.5,
];
let is_ccw = self.winding_order() == WindingOrder::CounterClockwise;
let indices = if is_ccw {
Indices::U32(vec![0, 1, 2])
} else {
Indices::U32(vec![0, 2, 1])
};
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 From<Triangle2d> for Mesh {
fn from(triangle: Triangle2d) -> Self {
triangle.mesh()
}
}
impl Meshable for Rectangle {
type Output = Mesh;
fn mesh(&self) -> Self::Output {
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 From<Rectangle> for Mesh {
fn from(rectangle: Rectangle) -> Self {
rectangle.mesh()
}
}
#[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
}
pub 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 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()
}
}
impl From<Capsule2dMeshBuilder> for Mesh {
fn from(capsule: Capsule2dMeshBuilder) -> Self {
capsule.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[..]);
}
}