From 2f4c494c340d8ca349d72c7f7253868b8b222235 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sat, 23 Jan 2021 10:43:11 +1100 Subject: [PATCH] Support rendering 3D trimesh, with example --- bevy_rapier3d/examples/static_trimesh3.rs | 175 ++++++++++++++++++++++ src/render/systems.rs | 64 +++++++- 2 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 bevy_rapier3d/examples/static_trimesh3.rs diff --git a/bevy_rapier3d/examples/static_trimesh3.rs b/bevy_rapier3d/examples/static_trimesh3.rs new file mode 100644 index 0000000000000..4f1d6aa64295d --- /dev/null +++ b/bevy_rapier3d/examples/static_trimesh3.rs @@ -0,0 +1,175 @@ +extern crate rapier3d as rapier; // For the debug UI. + +use std::f32::consts::TAU; + +use bevy::prelude::*; +use bevy::render::pass::ClearColor; +use bevy_rapier3d::physics::RapierPhysicsPlugin; +use bevy_rapier3d::render::RapierRenderPlugin; +use rapier3d::dynamics::{IntegrationParameters, RigidBodyBuilder}; +use rapier3d::geometry::ColliderBuilder; +use rapier3d::pipeline::PhysicsPipeline; +use ui::DebugUiPlugin; + +#[path = "../../src_debug_ui/mod.rs"] +mod ui; + +fn main() { + App::build() + .add_resource(ClearColor(Color::rgb( + 0xF9 as f32 / 255.0, + 0xF9 as f32 / 255.0, + 0xFF as f32 / 255.0, + ))) + .add_resource(Msaa::default()) + .add_plugins(DefaultPlugins) + .add_plugin(bevy_winit::WinitPlugin::default()) + .add_plugin(bevy_wgpu::WgpuPlugin::default()) + .add_plugin(RapierPhysicsPlugin) + .add_plugin(RapierRenderPlugin) + .add_plugin(DebugUiPlugin) + .add_startup_system(setup_graphics.system()) + .add_startup_system(setup_physics.system()) + .add_startup_system(enable_physics_profiling.system()) + .add_resource(BallState::default()) + .add_system(ball_spawner.system()) + .run(); +} + +fn enable_physics_profiling(mut pipeline: ResMut) { + pipeline.counters.enable() +} + +fn setup_graphics(commands: &mut Commands) { + commands + .spawn(LightBundle { + transform: Transform::from_translation(Vec3::new(1000.0, 100.0, 2000.0)), + ..Default::default() + }) + .spawn(Camera3dBundle { + transform: Transform::from_matrix(Mat4::face_toward( + Vec3::new(-15.0, 8.0, 15.0), + Vec3::new(-5.0, 0.0, 5.0), + Vec3::new(0.0, 1.0, 0.0), + )), + ..Default::default() + }); +} + +fn ramp_size() -> Vec3 { + Vec3::new(10.0, 1.0, 1.0) +} + +pub fn setup_physics(commands: &mut Commands) { + use bevy_rapier3d::na::Point3; + + // Create the ramp. + let mut vertices: Vec> = Vec::new(); + let mut indices: Vec> = Vec::new(); + let segments = 32; + let ramp_size = ramp_size(); + for i in 0..=segments { + // Half cosine wave vertically (with middle of low point at origin) + let x = i as f32 / segments as f32 * ramp_size.x; + let y = (-(i as f32 / segments as f32 * TAU / 2.0).cos() + 1.0) * ramp_size.y / 2.0; + vertices.push(Point3::new(x, y, -ramp_size.z / 2.0)); + vertices.push(Point3::new(x, y, ramp_size.z / 2.0)); + } + for i in 0..segments { + // Two triangles making up a flat quad for each segment of the ramp. + indices.push(Point3::new(2 * i + 0, 2 * i + 1, 2 * i + 2)); + indices.push(Point3::new(2 * i + 2, 2 * i + 1, 2 * i + 3)); + } + let rigid_body = RigidBodyBuilder::new_static().translation(0.0, 0.0, 0.0); + let collider = ColliderBuilder::trimesh(vertices, indices); + commands.spawn((rigid_body, collider)); + + // Create a bowl with a cosine cross-section, + // so that we can join the end of the ramp smoothly + // to the lip of the bowl. + let mut vertices: Vec> = Vec::new(); + let mut indices: Vec> = Vec::new(); + let segments = 32; + let bowl_size = Vec3::new(10.0, 3.0, 10.0); + for ix in 0..=segments { + for iz in 0..=segments { + // Map x and y into range [-1.0, 1.0]; + let shifted_z = (iz as f32 / segments as f32 - 0.5) * 2.0; + let shifted_x = (ix as f32 / segments as f32 - 0.5) * 2.0; + // Clamp radius at 1.0 or lower so the bowl has a flat lip near the corners. + let clamped_radius = (shifted_z.powi(2) + shifted_x.powi(2)).sqrt().min(1.0); + let x = shifted_x * bowl_size.x / 2.0; + let z = shifted_z * bowl_size.z / 2.0; + let y = ((clamped_radius - 0.5) * TAU / 2.0).sin() * bowl_size.y / 2.0; + vertices.push(Point3::new(x, y, z)); + } + } + for ix in 0..segments { + for iz in 0..segments { + // Start of the two relevant rows of vertices. + let row0 = ix * (segments + 1); + let row1 = (ix + 1) * (segments + 1); + // Two triangles making up a not-very-flat quad for each segment of the bowl. + indices.push(Point3::new(row0 + iz + 0, row0 + iz + 1, row1 + iz + 0)); + indices.push(Point3::new(row1 + iz + 0, row0 + iz + 1, row1 + iz + 1)); + } + } + // Position so ramp connects smoothly + // to one edge of the lip of the bowl. + let rigid_body = RigidBodyBuilder::new_static().translation( + -bowl_size.x / 2.0, + -bowl_size.y / 2.0, + bowl_size.z / 2.0 - ramp_size.z / 2.0, + ); + let collider = ColliderBuilder::trimesh(vertices, indices); + commands.spawn((rigid_body, collider)); +} + +struct BallState { + seconds_until_next_spawn: f32, + seconds_between_spawns: f32, + balls_spawned: usize, + max_balls: usize, +} + +impl Default for BallState { + fn default() -> Self { + Self { + seconds_until_next_spawn: 0.5, + seconds_between_spawns: 2.0, + balls_spawned: 0, + max_balls: 10, + } + } +} + +fn ball_spawner( + commands: &mut Commands, + integration_parameters: Res, + mut ball_state: ResMut, +) { + if ball_state.balls_spawned >= ball_state.max_balls { + return; + } + + // NOTE: The timing here only works properly with `time_dependent_number_of_timesteps` + // disabled, as it is for examples. + ball_state.seconds_until_next_spawn -= integration_parameters.dt(); + if ball_state.seconds_until_next_spawn > 0.0 { + return; + } + ball_state.seconds_until_next_spawn = ball_state.seconds_between_spawns; + + // Spawn a ball near the top of the ramp. + let ramp_size = ramp_size(); + let rad = 0.3; + let rigid_body = RigidBodyBuilder::new_dynamic().translation( + ramp_size.x * 0.9, + ramp_size.y / 2.0 + rad * 3.0, + 0.0, + ); + let collider = ColliderBuilder::ball(rad).restitution(0.5); + commands.spawn((rigid_body, collider)); + + ball_state.balls_spawned += 1; +} diff --git a/src/render/systems.rs b/src/render/systems.rs index 148c185f7b29f..1aea1e12377ca 100644 --- a/src/render/systems.rs +++ b/src/render/systems.rs @@ -1,7 +1,6 @@ use crate::physics::{ColliderHandleComponent, RapierConfiguration}; use crate::render::RapierRenderColor; use bevy::prelude::*; -#[cfg(feature = "dim2")] use bevy::render::mesh::{Indices, VertexAttributeValues}; use rapier::dynamics::RigidBodySet; use rapier::geometry::{ColliderSet, ShapeType}; @@ -102,6 +101,69 @@ pub fn create_collider_renders_system( ))); mesh } + #[cfg(feature = "dim3")] + ShapeType::Trimesh => { + let mut mesh = + Mesh::new(bevy::render::pipeline::PrimitiveTopology::TriangleList); + let trimesh = shape.as_trimesh().unwrap(); + mesh.set_attribute( + Mesh::ATTRIBUTE_POSITION, + VertexAttributeValues::from( + trimesh + .vertices() + .iter() + .map(|vertex| [vertex.x, vertex.y, vertex.z]) + .collect::>(), + ), + ); + // Compute vertex normals by averaging the normals + // of every triangle they appear in. + // NOTE: This is a bit shonky, but good enough for visualisation. + let verts = trimesh.vertices(); + let mut normals: Vec = vec![Vec3::zero(); trimesh.vertices().len()]; + for triangle in trimesh.indices().iter() { + let ab = verts[triangle[1] as usize] - verts[triangle[0] as usize]; + let ac = verts[triangle[2] as usize] - verts[triangle[0] as usize]; + let normal = ab.cross(&ac); + // Contribute this normal to each vertex in the triangle. + for i in 0..3 { + normals[triangle[i] as usize] += + Vec3::new(normal.x, normal.y, normal.z); + } + } + let normals: Vec<[f32; 3]> = normals + .iter() + .map(|normal| { + let normal = normal.normalize(); + [normal.x, normal.y, normal.z] + }) + .collect(); + mesh.set_attribute( + Mesh::ATTRIBUTE_NORMAL, + VertexAttributeValues::from(normals), + ); + // There's nothing particularly meaningful we can do + // for this one without knowing anything about the overall topology. + mesh.set_attribute( + Mesh::ATTRIBUTE_UV_0, + VertexAttributeValues::from( + trimesh + .vertices() + .iter() + .map(|_vertex| [0.0, 0.0]) + .collect::>(), + ), + ); + mesh.set_indices(Some(Indices::U32( + trimesh + .indices() + .iter() + .flat_map(|triangle| triangle.iter()) + .cloned() + .collect(), + ))); + mesh + } _ => continue, };