From 596f9ea02a07c626a7968bf58cedcdd98c1aaf2c Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Wed, 15 Jan 2025 17:20:49 +0100 Subject: [PATCH] wip parry_to_bevy_mesh --- bevy_rapier2d/Cargo.toml | 1 + bevy_rapier3d/Cargo.toml | 1 + src/geometry/mod.rs | 2 + src/geometry/to_bevy_mesh.rs | 209 +++++++++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+) create mode 100644 src/geometry/to_bevy_mesh.rs diff --git a/bevy_rapier2d/Cargo.toml b/bevy_rapier2d/Cargo.toml index 4a626709..76b49eaf 100644 --- a/bevy_rapier2d/Cargo.toml +++ b/bevy_rapier2d/Cargo.toml @@ -50,6 +50,7 @@ serde-serialize = ["rapier2d/serde-serialize", "bevy/serialize", "serde"] enhanced-determinism = ["rapier2d/enhanced-determinism"] headless = [] async-collider = ["bevy/bevy_asset", "bevy/bevy_scene", "bevy/bevy_render"] +collider-to-bevy-mesh = ["bevy/bevy_render"] [dependencies] bevy = { version = "0.15", default-features = false } diff --git a/bevy_rapier3d/Cargo.toml b/bevy_rapier3d/Cargo.toml index b5d18c47..b39cad43 100644 --- a/bevy_rapier3d/Cargo.toml +++ b/bevy_rapier3d/Cargo.toml @@ -51,6 +51,7 @@ serde-serialize = ["rapier3d/serde-serialize", "bevy/serialize", "serde"] enhanced-determinism = ["rapier3d/enhanced-determinism"] headless = [] async-collider = ["bevy/bevy_asset", "bevy/bevy_scene", "bevy/bevy_render"] +collider-to-bevy-mesh = ["bevy/bevy_render"] [dependencies] bevy = { version = "0.15", default-features = false } diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index f89198e3..1cfe1037 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -12,6 +12,8 @@ mod collider; mod collider_impl; /// Wrappers around Rapier shapes to access their properties. pub mod shape_views; +#[cfg(feature = "collider-to-bevy-mesh")] +pub mod to_bevy_mesh; /// Result of the projection of a point on a shape. #[derive(Copy, Clone, Debug, PartialEq)] diff --git a/src/geometry/to_bevy_mesh.rs b/src/geometry/to_bevy_mesh.rs new file mode 100644 index 00000000..5d8b26fc --- /dev/null +++ b/src/geometry/to_bevy_mesh.rs @@ -0,0 +1,209 @@ +use super::Collider; +use bevy::{ + asset::RenderAssetUsages, + prelude::{Mesh, MeshBuilder, Segment3d}, + render::mesh::Indices, +}; +use rapier::prelude::{Shape, TriMesh, TypedShape}; + +pub fn typed_shape_to_mesh(typed_shape: TypedShape) -> Mesh { + match typed_shape { + rapier::prelude::TypedShape::Ball(ball) => { + let radius = ball.radius; + let mesh = bevy::render::mesh::SphereMeshBuilder::new( + radius, + bevy::render::mesh::SphereKind::Ico { subdivisions: 10 }, + ); + mesh.build() + } + rapier::prelude::TypedShape::Cuboid(cuboid) => { + let half_extents = cuboid.half_extents; + #[cfg(feature = "dim2")] + let mesh = bevy::prelude::Rectangle::new(half_extents.x * 2.0, half_extents.y * 2.0); + #[cfg(feature = "dim3")] + let mesh = bevy::prelude::Cuboid::new( + half_extents.x * 2.0, + half_extents.y * 2.0, + half_extents.z * 2.0, + ); + Mesh::from(mesh) + } + rapier::prelude::TypedShape::Capsule(capsule) => { + let radius = capsule.radius; + let half_height = capsule.half_height(); + #[cfg(feature = "dim2")] + let mesh = bevy::render::mesh::Capsule2dMeshBuilder::new(radius, half_height, 10); + #[cfg(feature = "dim3")] + let mesh = bevy::render::mesh::Capsule3dMeshBuilder::new(radius, half_height, 10, 10); + mesh.build() + } + rapier::prelude::TypedShape::Segment(segment) => { + todo!("Segment shape not implemented yet, how to represent it ? A LineStrip?"); + } + rapier::prelude::TypedShape::Triangle(triangle) => { + let a = triangle.a.coords; + let b = triangle.b.coords; + let c = triangle.c.coords; + let mesh = bevy::prelude::Triangle3d::new( + bevy::prelude::Vec3::new(a.x, a.y, 0.0), + bevy::prelude::Vec3::new(b.x, b.y, 0.0), + bevy::prelude::Vec3::new(c.x, c.y, 0.0), + ); + mesh.into() + } + rapier::prelude::TypedShape::TriMesh(tri_mesh) => { + let vertices = tri_mesh.vertices(); + let vertices: Vec<_> = vertices.iter().map(|pos| [pos.x, pos.y, 0.0]).collect(); + let indices = tri_mesh.indices(); + let mesh = Mesh::new( + bevy::render::mesh::PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_indices(Indices::U32(indices.iter().cloned().flatten().collect())); + let mesh = mesh.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + + mesh.into() + } + rapier::prelude::TypedShape::Polyline(polyline) => { + todo!("Polyline shape not implemented yet, how to represent it ? BoxedPolyline3d is only a primitive."); + } + rapier::prelude::TypedShape::HalfSpace(half_space) => { + todo!("HalfSpace shape not implemented yet, how to represent it ? its infinite property makes it difficult."); + } + rapier::prelude::TypedShape::HeightField(height_field) => { + #[cfg(feature = "dim2")] + todo!("HeightField for 2d not implemented yet, how to represent it ? its effectively a line."); + #[cfg(feature = "dim3")] + { + // FIXME: we could use TriMesh::From(height_field), but that would clone, we should fix that in parry. + let (vtx, idx) = height_field.to_trimesh(); + let tri_mesh = TriMesh::new(vtx, idx); + + // From Trimesh: + let vertices = tri_mesh.vertices(); + let vertices: Vec<_> = vertices.iter().map(|pos| [pos.x, pos.y, 0.0]).collect(); + let indices = tri_mesh.indices(); + let mesh = Mesh::new( + bevy::render::mesh::PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_indices(Indices::U32(indices.iter().cloned().flatten().collect())); + let mesh = mesh.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + + mesh.into() + } + } + rapier::prelude::TypedShape::Compound(compound) => { + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + for shape in compound.shapes() { + let typed_shape = shape.1.as_typed_shape(); + let mesh = typed_shape_to_mesh(typed_shape); + + assert!(mesh.primitive_topology() == bevy::render::mesh::PrimitiveTopology::TriangleList, + "Compound shape mesh conversion does not support shapes not converting to PrimitiveTopology::TriangleList."); + + vertices.append( + &mut mesh + .attribute(Mesh::ATTRIBUTE_POSITION.id) + .unwrap() + .as_float3() + .unwrap() + .iter() + .cloned() + .flatten() + .collect::>(), + ); + indices.append(&mut mesh.indices().unwrap().iter().collect::>()); + } + let mesh = Mesh::new( + bevy::render::mesh::PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_indices(Indices::U32(indices.iter().map(|i| *i as u32).collect())); + let mesh = mesh.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + mesh.into() + } + #[cfg(feature = "dim2")] + rapier::prelude::TypedShape::ConvexPolygon(convex_polygon) => { + let vertices = convex_polygon.points(); + let vertices: Vec<_> = vertices.iter().map(|pos| [pos.x, pos.y, 0.0]).collect(); + + let indices = (1..vertices.len() as u32 - 1) + .flat_map(|i| vec![0, i, i + 1]) + .collect::>(); + let mesh = Mesh::new( + bevy::render::mesh::PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_indices(Indices::U32(indices)); + let mesh = mesh.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + + mesh.into() + } + #[cfg(feature = "dim3")] + rapier::prelude::TypedShape::ConvexPolyhedron(convex_polyhedron) => { + let vertices = convex_polyhedron.points(); + let vertices: Vec<_> = vertices.iter().map(|pos| [pos.x, pos.y, pos.z]).collect(); + + let indices = (1..vertices.len() as u32 - 1) + .flat_map(|i| vec![0, i, i + 1]) + .collect::>(); + let mesh = Mesh::new( + bevy::render::mesh::PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_indices(Indices::U32(indices)); + let mesh = mesh.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + + mesh.into() + } + #[cfg(feature = "dim3")] + rapier::prelude::TypedShape::Cone(cone) => { + let radius = cone.radius; + let half_height = cone.half_height; + // TODO: implement Meshable for all TypedShape variants, that probably will have to be wrapped in a new type. + let mesh = bevy::render::mesh::ConeMeshBuilder::new(radius, half_height, 10); + mesh.build() + } + #[cfg(feature = "dim3")] + rapier::prelude::TypedShape::Cylinder(cylinder) => { + let radius = cylinder.radius; + let half_height = cylinder.half_height; + let mesh = bevy::render::mesh::CylinderMeshBuilder::new(radius, half_height, 10); + mesh.build() + } + #[cfg(feature = "dim3")] + rapier::prelude::TypedShape::RoundCone(round_cone) => { + todo!("parry doesn't have easy to use functions to convert RoundShapes to a mesh."); + } + #[cfg(feature = "dim3")] + rapier::prelude::TypedShape::RoundCylinder(round_cylinder) => { + todo!("parry doesn't have easy to use functions to convert RoundShapes to a mesh."); + } + #[cfg(feature = "dim2")] + rapier::prelude::TypedShape::RoundConvexPolygon(round_shape) => { + todo!("parry doesn't have easy to use functions to convert RoundShapes to a mesh."); + } + #[cfg(feature = "dim3")] + rapier::prelude::TypedShape::RoundConvexPolyhedron(round_shape) => { + todo!("parry doesn't have easy to use functions to convert RoundShapes to a mesh."); + } + rapier::prelude::TypedShape::RoundCuboid(round_shape) => { + todo!("parry doesn't have easy to use functions to convert RoundShapes to a mesh."); + } + rapier::prelude::TypedShape::RoundTriangle(round_shape) => { + todo!("parry doesn't have easy to use functions to convert RoundShapes to a mesh."); + } + rapier::prelude::TypedShape::Custom(shape) => { + todo!("I'm not sure how to convert a custom shape to a mesh."); + } + } +} + +impl From for Mesh { + fn from(shape: Collider) -> Self { + let typed_shape = shape.raw.as_typed_shape(); + typed_shape_to_mesh(typed_shape) + } +}