Skip to content

Commit

Permalink
Merge pull request bevyengine#51 from jeffparsons/render_trimesh_3d
Browse files Browse the repository at this point in the history
Support rendering 3D trimesh, with example
  • Loading branch information
sebcrozet authored Jan 23, 2021
2 parents 42faa91 + 2f4c494 commit a838854
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 1 deletion.
175 changes: 175 additions & 0 deletions bevy_rapier3d/examples/static_trimesh3.rs
Original file line number Diff line number Diff line change
@@ -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<PhysicsPipeline>) {
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<Point3<f32>> = Vec::new();
let mut indices: Vec<Point3<u32>> = 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<Point3<f32>> = Vec::new();
let mut indices: Vec<Point3<u32>> = 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<IntegrationParameters>,
mut ball_state: ResMut<BallState>,
) {
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;
}
64 changes: 63 additions & 1 deletion src/render/systems.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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::<Vec<_>>(),
),
);
// 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<Vec3> = 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::<Vec<_>>(),
),
);
mesh.set_indices(Some(Indices::U32(
trimesh
.indices()
.iter()
.flat_map(|triangle| triangle.iter())
.cloned()
.collect(),
)));
mesh
}
_ => continue,
};

Expand Down

0 comments on commit a838854

Please sign in to comment.