diff --git a/Cargo.toml b/Cargo.toml index 8310400..366a800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "geo-bevy" description = "Generate Bevy meshes from `geo` types" -version = "0.3.0" +version = "0.4.0" authors = ["Corey Farwell "] edition = "2021" repository = "https://github.com/frewsxcv/geo-bevy" @@ -11,3 +11,4 @@ license = "MIT OR Apache-2.0" bevy-earcutr = "0.9" bevy = { version = "0.10", default-features = false, features = ["bevy_render"] } geo = "0.25" +geo-types = "0.7" diff --git a/src/build_mesh.rs b/src/build_mesh.rs new file mode 100644 index 0000000..64c2ba0 --- /dev/null +++ b/src/build_mesh.rs @@ -0,0 +1,143 @@ +use geo_types::*; +use std::num::TryFromIntError; + +pub trait BuildMesh { + fn build(self) -> Option; +} + +#[derive(Default)] +pub struct BuildBevyMeshesContext { + pub point_mesh_builder: crate::point::PointMeshBuilder, + pub line_string_mesh_builder: crate::line_string::LineStringMeshBuilder, + pub polygon_mesh_builder: crate::polygon::PolygonMeshBuilder, +} + +pub trait BuildBevyMeshes { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError>; +} + +impl BuildBevyMeshes for Point { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + ctx.point_mesh_builder.add_point(self) + } +} + +impl BuildBevyMeshes for LineString { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + ctx.line_string_mesh_builder.add_line_string(self) + } +} + +impl BuildBevyMeshes for Polygon { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + ctx.polygon_mesh_builder.add_polygon(self) + } +} + +impl BuildBevyMeshes for MultiPoint { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + for point in &self.0 { + point.populate_mesh_builders(ctx)?; + } + Ok(()) + } +} + +impl BuildBevyMeshes for MultiLineString { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + for line_string in &self.0 { + line_string.populate_mesh_builders(ctx)?; + } + Ok(()) + } +} + +impl BuildBevyMeshes for MultiPolygon { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + for polygon in &self.0 { + polygon.populate_mesh_builders(ctx)?; + } + Ok(()) + } +} + +impl BuildBevyMeshes for Line { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + LineString::from(self).populate_mesh_builders(ctx) + } +} + +impl BuildBevyMeshes for Triangle { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + self.to_polygon().populate_mesh_builders(ctx) + } +} + +impl BuildBevyMeshes for Rect { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + self.to_polygon().populate_mesh_builders(ctx) + } +} + +impl BuildBevyMeshes for Geometry { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + match self { + Geometry::Point(g) => g.populate_mesh_builders(ctx)?, + Geometry::Line(g) => g.populate_mesh_builders(ctx)?, + Geometry::LineString(g) => g.populate_mesh_builders(ctx)?, + Geometry::Polygon(g) => g.populate_mesh_builders(ctx)?, + Geometry::MultiPoint(g) => g.populate_mesh_builders(ctx)?, + Geometry::MultiLineString(g) => g.populate_mesh_builders(ctx)?, + Geometry::MultiPolygon(g) => g.populate_mesh_builders(ctx)?, + Geometry::GeometryCollection(g) => g.populate_mesh_builders(ctx)?, + Geometry::Triangle(g) => g.populate_mesh_builders(ctx)?, + Geometry::Rect(g) => g.populate_mesh_builders(ctx)?, + }; + Ok(()) + } +} + +impl BuildBevyMeshes for GeometryCollection { + fn populate_mesh_builders( + &self, + ctx: &mut BuildBevyMeshesContext, + ) -> Result<(), TryFromIntError> { + for g in self { + g.populate_mesh_builders(ctx)?; + } + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index cc88fed..039b38d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,226 +1,103 @@ +use bevy::prelude::{info_span, Mesh}; +use build_mesh::{BuildBevyMeshes, BuildMesh}; +use geo_types::geometry::*; +use line_string::LineStringMeshBuilder; +use polygon::PolygonMeshBuilder; use std::num::TryFromIntError; -use bevy::prelude::*; -use geo::algorithm::coords_iter::CoordsIter; +pub use polygon::PolygonMesh; + +mod build_mesh; mod line_string; mod point; +mod polygon; -pub enum PreparedMesh { - Point(Vec), - LineString { mesh: Mesh, color: Color }, - Polygon { mesh: Mesh, color: Color }, -} - -type Vertex = [f32; 3]; // [x, y, z] - -fn build_mesh_from_vertices( - primitive_topology: bevy::render::render_resource::PrimitiveTopology, - vertices: Vec, - indices: Vec, -) -> Mesh { - let num_vertices = vertices.len(); - let mut mesh = Mesh::new(primitive_topology); - mesh.set_indices(Some(bevy::render::mesh::Indices::U32(indices))); - mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); - - let normals = vec![[0.0, 0.0, 0.0]; num_vertices]; - let uvs = vec![[0.0, 0.0]; num_vertices]; - - mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); - - mesh -} - -#[derive(Default)] -pub struct BuildBevyMeshesContext { - point_mesh_builder: point::PointMeshBuilder, - line_string_mesh_builder: line_string::LineStringMeshBuilder, - polygon_mesh_builder: bevy_earcutr::PolygonMeshBuilder, - polygon_border_mesh_builder: line_string::LineStringMeshBuilder, +pub fn line_to_mesh(line: &Line) -> Result, TryFromIntError> { + line_string_to_mesh(&line.into()) } -pub fn build_bevy_meshes( - geo: &G, - color: Color, -) -> Result, TryFromIntError> { - let mut ctx = BuildBevyMeshesContext::default(); - - info_span!("Populating Bevy mesh builder").in_scope(|| geo.populate_mesh_builders(&mut ctx))?; - - info_span!("Building Bevy meshes").in_scope(|| { - Ok([ - ctx.point_mesh_builder.build(), - ctx.line_string_mesh_builder.build(color), - ctx.polygon_mesh_builder - .build() - .map(|mesh| PreparedMesh::Polygon { mesh, color }), - ctx.polygon_border_mesh_builder.build(Color::BLACK), - ] - .into_iter() - .flatten()) - }) +pub fn line_string_to_mesh(line_string: &LineString) -> Result, TryFromIntError> { + let mut mesh_builder = LineStringMeshBuilder::default(); + mesh_builder.add_line_string(line_string)?; + Ok(mesh_builder.into()) } -pub trait BuildBevyMeshes { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError>; -} +pub fn multi_line_string_to_mesh( + multi_line_string: &MultiLineString, +) -> Result, TryFromIntError> { + let line_strings = &multi_line_string.0; + let mut line_string_meshes = Vec::with_capacity(line_strings.len()); -impl BuildBevyMeshes for geo::Point { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - ctx.point_mesh_builder.add_point(self) + for line_string in line_strings { + if let Some(line_string_mesh) = line_string_to_mesh(line_string)? { + line_string_meshes.push(line_string_mesh); + } } -} -impl BuildBevyMeshes for geo::LineString { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - ctx.line_string_mesh_builder.add_line_string(self) - } + Ok(line_string_meshes) } -impl BuildBevyMeshes for geo::Polygon { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - ctx.polygon_mesh_builder - .add_earcutr_input(polygon_to_earcutr_input(self)); - ctx.polygon_border_mesh_builder - .add_line_string(self.exterior())?; - for interior in self.interiors() { - ctx.polygon_border_mesh_builder.add_line_string(interior)?; - } - Ok(()) - } +pub fn polygon_to_mesh(polygon: &Polygon) -> Result, TryFromIntError> { + let mut mesh_builder = PolygonMeshBuilder::default(); + mesh_builder.add_polygon(polygon)?; + Ok(mesh_builder.into()) } -impl BuildBevyMeshes for geo::MultiPoint { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - for point in &self.0 { - point.populate_mesh_builders(ctx)?; +pub fn multi_polygon_to_mesh( + multi_polygon: &MultiPolygon, +) -> Result, TryFromIntError> { + let polygons = &multi_polygon.0; + let mut polygon_meshes = Vec::with_capacity(polygons.len()); + for polygon in polygons { + if let Some(polygon_mesh) = polygon_to_mesh(polygon)? { + polygon_meshes.push(polygon_mesh); } - Ok(()) } -} -impl BuildBevyMeshes for geo::MultiLineString { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - for line_string in &self.0 { - line_string.populate_mesh_builders(ctx)?; - } - Ok(()) - } + Ok(polygon_meshes) } -impl BuildBevyMeshes for geo::MultiPolygon { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - for polygon in &self.0 { - polygon.populate_mesh_builders(ctx)?; - } - Ok(()) - } +pub fn rect_to_mesh(rect: &Rect) -> Result, TryFromIntError> { + polygon_to_mesh(&rect.to_polygon()) } -impl BuildBevyMeshes for geo::Line { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - geo::LineString::new(vec![self.start, self.end]).populate_mesh_builders(ctx) - } +pub fn triangle_to_mesh(triangle: &Triangle) -> Result, TryFromIntError> { + polygon_to_mesh(&triangle.to_polygon()) } -impl BuildBevyMeshes for geo::Triangle { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - self.to_polygon().populate_mesh_builders(ctx) - } -} +pub fn geometry_to_mesh(geometry: &Geometry) -> Result, TryFromIntError> { + let mut ctx = build_mesh::BuildBevyMeshesContext::default(); -impl BuildBevyMeshes for geo::Rect { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - self.to_polygon().populate_mesh_builders(ctx) - } -} + info_span!("Populating Bevy mesh builder") + .in_scope(|| geometry.populate_mesh_builders(&mut ctx))?; -impl BuildBevyMeshes for geo::Geometry { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - match self { - geo::Geometry::Point(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::Line(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::LineString(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::Polygon(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::MultiPoint(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::MultiLineString(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::MultiPolygon(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::GeometryCollection(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::Triangle(g) => g.populate_mesh_builders(ctx)?, - geo::Geometry::Rect(g) => g.populate_mesh_builders(ctx)?, - }; - Ok(()) - } + info_span!("Building Bevy meshes").in_scope(|| { + Ok([ + ctx.point_mesh_builder.build(), + ctx.line_string_mesh_builder.build(), + ctx.polygon_mesh_builder.build(), + ] + .into_iter() + .find(|prepared_mesh| prepared_mesh.is_some()) + .unwrap_or_default()) + }) } -impl BuildBevyMeshes for geo::GeometryCollection { - fn populate_mesh_builders( - &self, - ctx: &mut BuildBevyMeshesContext, - ) -> Result<(), TryFromIntError> { - for g in self { - g.populate_mesh_builders(ctx)?; +pub fn geometry_collection_to_mesh( + geometry_collection: &GeometryCollection, +) -> Result, TryFromIntError> { + let mut geometry_meshes = Vec::with_capacity(geometry_collection.len()); + for geometry in geometry_collection { + if let Some(geometry_mesh) = geometry_to_mesh(geometry)? { + geometry_meshes.push(geometry_mesh); } - Ok(()) } -} - -fn polygon_to_earcutr_input(polygon: &geo::Polygon) -> bevy_earcutr::EarcutrInput { - let mut vertices = Vec::with_capacity(polygon.coords_count() * 2); - let mut interior_indices = Vec::with_capacity(polygon.interiors().len()); - debug_assert!(polygon.exterior().0.len() >= 4); - flat_line_string_coords_2(polygon.exterior(), &mut vertices); - - for interior in polygon.interiors() { - debug_assert!(interior.0.len() >= 4); - interior_indices.push(vertices.len() / 2); - flat_line_string_coords_2(interior, &mut vertices); - } - - bevy_earcutr::EarcutrInput { - vertices, - interior_indices, - } + Ok(geometry_meshes) } -fn flat_line_string_coords_2(line_string: &geo::LineString, vertices: &mut Vec) { - for coord in &line_string.0 { - vertices.push(coord.x); - vertices.push(coord.y); - } +pub enum GeometryMesh { + Point(Vec), + LineString(Mesh), + Polygon(polygon::PolygonMesh), } diff --git a/src/line_string.rs b/src/line_string.rs index 94db32c..ccd93d5 100644 --- a/src/line_string.rs +++ b/src/line_string.rs @@ -1,7 +1,8 @@ -use crate::Vertex; -use bevy::prelude::Color; +use bevy::prelude::Mesh; use std::num; +type Vertex = [f32; 3]; // [x, y, z] + #[derive(Default)] pub struct LineStringMeshBuilder { vertices: Vec, @@ -28,19 +29,39 @@ impl LineStringMeshBuilder { } Ok(()) } +} + +impl From for Mesh { + fn from(line_string_builder: LineStringMeshBuilder) -> Self { + let vertices = line_string_builder.vertices; + let indices = line_string_builder.indices; + let num_vertices = vertices.len(); + let mut mesh = Mesh::new(bevy::render::render_resource::PrimitiveTopology::LineList); + mesh.set_indices(Some(bevy::render::mesh::Indices::U32(indices))); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); - pub fn build(self, color: Color) -> Option { - if self.vertices.is_empty() { + let normals = vec![[0.0, 0.0, 0.0]; num_vertices]; + let uvs = vec![[0.0, 0.0]; num_vertices]; + + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + + mesh + } +} + +impl From for Option { + fn from(line_string_mesh_builder: LineStringMeshBuilder) -> Self { + if line_string_mesh_builder.vertices.is_empty() { None } else { - Some(crate::PreparedMesh::LineString { - mesh: crate::build_mesh_from_vertices( - bevy::render::render_resource::PrimitiveTopology::LineList, - self.vertices, - self.indices, - ), - color, - }) + Some(line_string_mesh_builder.into()) } } } + +impl crate::build_mesh::BuildMesh for LineStringMeshBuilder { + fn build(self) -> Option { + Option::::from(self).map(crate::GeometryMesh::LineString) + } +} diff --git a/src/point.rs b/src/point.rs index a1aa3ba..6ca5491 100644 --- a/src/point.rs +++ b/src/point.rs @@ -11,12 +11,14 @@ impl PointMeshBuilder { self.points.push(*point); Ok(()) } +} - pub fn build(self) -> Option { +impl crate::build_mesh::BuildMesh for PointMeshBuilder { + fn build(self) -> Option { if self.points.is_empty() { None } else { - Some(crate::PreparedMesh::Point(self.points)) + Some(crate::GeometryMesh::Point(self.points)) } } } diff --git a/src/polygon.rs b/src/polygon.rs new file mode 100644 index 0000000..6bfa576 --- /dev/null +++ b/src/polygon.rs @@ -0,0 +1,81 @@ +use crate::{line_string::LineStringMeshBuilder, GeometryMesh}; +use bevy::prelude::Mesh; +use geo::CoordsIter; +use geo_types::{LineString, Polygon}; + +pub struct PolygonMesh { + pub mesh: Mesh, + pub exterior_mesh: Mesh, + pub interior_meshes: Vec, +} + +#[derive(Default)] +pub struct PolygonMeshBuilder { + polygon: bevy_earcutr::PolygonMeshBuilder, + exterior: LineStringMeshBuilder, + interiors: Vec, +} + +impl PolygonMeshBuilder { + pub fn add_polygon(&mut self, polygon: &Polygon) -> Result<(), std::num::TryFromIntError> { + self.polygon + .add_earcutr_input(Self::polygon_to_earcutr_input(polygon)); + self.exterior.add_line_string(polygon.exterior())?; + for interior in polygon.interiors() { + let mut interior_line_string_builder = LineStringMeshBuilder::default(); + interior_line_string_builder.add_line_string(interior)?; + self.interiors.push(interior_line_string_builder); + } + + Ok(()) + } + + fn polygon_to_earcutr_input(polygon: &Polygon) -> bevy_earcutr::EarcutrInput { + let mut vertices = Vec::with_capacity(polygon.coords_count() * 2); + let mut interior_indices = Vec::with_capacity(polygon.interiors().len()); + debug_assert!(polygon.exterior().0.len() >= 4); + + Self::flat_line_string_coords_2(polygon.exterior(), &mut vertices); + + for interior in polygon.interiors() { + debug_assert!(interior.0.len() >= 4); + interior_indices.push(vertices.len() / 2); + Self::flat_line_string_coords_2(interior, &mut vertices); + } + + bevy_earcutr::EarcutrInput { + vertices, + interior_indices, + } + } + + fn flat_line_string_coords_2(line_string: &LineString, vertices: &mut Vec) { + for coord in &line_string.0 { + vertices.push(coord.x); + vertices.push(coord.y); + } + } +} + +impl From for Option { + fn from(polygon_mesh_builder: PolygonMeshBuilder) -> Self { + polygon_mesh_builder + .polygon + .build() + .map(|polygon_mesh| PolygonMesh { + mesh: polygon_mesh, + exterior_mesh: polygon_mesh_builder.exterior.into(), + interior_meshes: polygon_mesh_builder + .interiors + .into_iter() + .map(|interior_builder| interior_builder.into()) + .collect(), + }) + } +} + +impl crate::build_mesh::BuildMesh for PolygonMeshBuilder { + fn build(self) -> Option { + Option::::from(self).map(GeometryMesh::Polygon) + } +}