diff --git a/crates/bevy_render/src/mesh/primitives/dim2.rs b/crates/bevy_render/src/mesh/primitives/dim2.rs index e10d7c845c..b928a195a2 100644 --- a/crates/bevy_render/src/mesh/primitives/dim2.rs +++ b/crates/bevy_render/src/mesh/primitives/dim2.rs @@ -1,14 +1,13 @@ use crate::{ + mesh::primitives::dim3::triangle3d, mesh::{Indices, Mesh}, render_asset::RenderAssetUsages, }; use super::Meshable; -use bevy_math::{ - primitives::{ - Annulus, Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder, - }, - Vec2, +use bevy_math::primitives::{ + Annulus, Capsule2d, Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, Triangle3d, + WindingOrder, }; use wgpu::PrimitiveTopology; @@ -317,25 +316,23 @@ impl Meshable for Triangle2d { type Output = Mesh; fn mesh(&self) -> Self::Output { - let [a, b, c] = self.vertices; + let vertices_3d = self.vertices.map(|v| v.extend(0.)); - let positions = vec![[a.x, a.y, 0.0], [b.x, b.y, 0.0], [c.x, c.y, 0.0]]; + let positions: Vec<_> = vertices_3d.into(); let normals = vec![[0.0, 0.0, 1.0]; 3]; - // The extents of the bounding box of the triangle, - // used to compute the UV coordinates of the points. - 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 uvs: Vec<_> = triangle3d::uv_coords(&Triangle3d::new( + vertices_3d[0], + vertices_3d[1], + vertices_3d[2], + )) + .into(); 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]) + Indices::U32(vec![2, 1, 0]) }; Mesh::new( diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index e2b337bb31..3f98557f70 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -4,6 +4,7 @@ mod cylinder; mod plane; mod sphere; mod torus; +pub(crate) mod triangle3d; pub use capsule::*; pub use cylinder::*; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/triangle3d.rs b/crates/bevy_render/src/mesh/primitives/dim3/triangle3d.rs new file mode 100644 index 0000000000..4eb0fba32f --- /dev/null +++ b/crates/bevy_render/src/mesh/primitives/dim3/triangle3d.rs @@ -0,0 +1,114 @@ +use bevy_math::{primitives::Triangle3d, Vec3}; +use wgpu::PrimitiveTopology; + +use crate::{ + mesh::{Indices, Mesh, Meshable}, + render_asset::RenderAssetUsages, +}; + +impl Meshable for Triangle3d { + type Output = Mesh; + + fn mesh(&self) -> Self::Output { + let positions: Vec<_> = self.vertices.into(); + let uvs: Vec<_> = uv_coords(self).into(); + + // Every vertex has the normal of the face of the triangle (or zero if the triangle is degenerate). + let normal: Vec3 = self.normal().map_or(Vec3::ZERO, |n| n.into()); + 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) + } +} + +/// 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 for Mesh { + fn from(triangle: Triangle3d) -> Self { + triangle.mesh() + } +} + +#[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.]); + } +}