Skip to content

Commit

Permalink
Support TriMesh collision shapes loaded from gltf
Browse files Browse the repository at this point in the history
  • Loading branch information
kvark committed Dec 11, 2023
1 parent acd2e4b commit addb2a8
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 14 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ bytemuck = { version = "1", features = ["derive"] }
choir = "0.7"
egui = "0.23"
glam = { version = "0.24", features = ["mint"] }
gltf = { version = "1.1", default-features = false }
log = "0.4"
mint = "0.5"
naga = { version = "0.14", features = ["wgsl-in", "span", "validate"] }
Expand Down Expand Up @@ -44,9 +45,11 @@ blade-asset = { version = "0.2", path = "blade-asset" }
blade-egui = { version = "0.2", path = "blade-egui" }
blade-graphics = { version = "0.3", path = "blade-graphics" }
blade-render = { version = "0.2", path = "blade-render" }
base64 = { workspace = true }
choir = { workspace = true }
colorsys = "0.6"
egui = { workspace = true }
gltf = { workspace = true }
nalgebra = { version = "0.32", features = ["mint"] }
log = { workspace = true }
mint = { workspace = true, features = ["serde"] }
Expand Down
2 changes: 1 addition & 1 deletion blade-render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ blade-macros = { version = "0.2.1", path = "../blade-macros" }
bytemuck = { workspace = true }
choir = { workspace = true }
exr = { version = "1.6", optional = true }
gltf = { version = "1.1", default-features = false, features = ["names", "utils"], optional = true }
gltf = { workspace = true, features = ["names", "utils"], optional = true }
glam = { workspace = true }
log = { workspace = true }
mikktspace = { package = "bevy_mikktspace", version = "0.10", optional = true }
Expand Down
26 changes: 22 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,28 @@ impl Default for Visual {

#[derive(serde::Deserialize)]
pub enum Shape {
Ball { radius: f32 },
Cylinder { half_height: f32, radius: f32 },
Cuboid { half: mint::Vector3<f32> },
ConvexHull { points: Vec<mint::Vector3<f32>> },
Ball {
radius: f32,
},
Cylinder {
half_height: f32,
radius: f32,
},
Cuboid {
half: mint::Vector3<f32>,
},
ConvexHull {
points: Vec<mint::Vector3<f32>>,
#[serde(default)]
border_radius: f32,
},
TriMesh {
model: String,
#[serde(default)]
convex: bool,
#[serde(default)]
border_radius: f32,
},
}

fn default_friction() -> f32 {
Expand Down
47 changes: 38 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use blade_graphics as gpu;
use std::{ops, path::Path, sync::Arc};

pub mod config;
mod trimesh;

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum JointKind {
Expand Down Expand Up @@ -273,7 +274,7 @@ impl Engine {
let render_config = blade_render::RenderConfig {
screen_size,
surface_format,
max_debug_lines: 1000,
max_debug_lines: 1 << 14,
};
let renderer = blade_render::Renderer::new(
command_encoder,
Expand Down Expand Up @@ -639,26 +640,54 @@ impl Engine {

let mut colliders = Vec::new();
for cc in config.colliders.iter() {
use rapier3d::geometry::ColliderBuilder;

let isometry = nalgebra::geometry::Isometry3::from_parts(
nalgebra::Vector3::from(cc.pos).into(),
make_quaternion(cc.rot),
);
let builder = match cc.shape {
config::Shape::Ball { radius } => rapier3d::geometry::ColliderBuilder::ball(radius),
config::Shape::Ball { radius } => ColliderBuilder::ball(radius),
config::Shape::Cylinder {
half_height,
radius,
} => rapier3d::geometry::ColliderBuilder::cylinder(half_height, radius),
config::Shape::Cuboid { half } => {
rapier3d::geometry::ColliderBuilder::cuboid(half.x, half.y, half.z)
}
config::Shape::ConvexHull { ref points } => {
} => ColliderBuilder::cylinder(half_height, radius),
config::Shape::Cuboid { half } => ColliderBuilder::cuboid(half.x, half.y, half.z),
config::Shape::ConvexHull {
ref points,
border_radius,
} => {
let pv = points
.iter()
.map(|p| nalgebra::Vector3::from(*p).into())
.collect::<Vec<_>>();
rapier3d::geometry::ColliderBuilder::convex_hull(&pv)
.expect("Unable to build convex full")
let result = if border_radius != 0.0 {
ColliderBuilder::round_convex_hull(&pv, border_radius)
} else {
ColliderBuilder::convex_hull(&pv)
};
result.expect("Unable to build convex hull shape")
}
config::Shape::TriMesh {
ref model,
convex,
border_radius,
} => {
let trimesh = trimesh::load(&format!("data/{}", model));
if convex && border_radius != 0.0 {
ColliderBuilder::round_convex_mesh(
trimesh.points,
&trimesh.triangles,
border_radius,
)
.expect("Unable to build rounded convex mesh")
} else if convex {
ColliderBuilder::convex_mesh(trimesh.points, &trimesh.triangles)
.expect("Unable to build convex mesh")
} else {
assert_eq!(border_radius, 0.0);
ColliderBuilder::trimesh(trimesh.points, trimesh.triangles)
}
}
};
let collider = builder
Expand Down
102 changes: 102 additions & 0 deletions src/trimesh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use std::unimplemented;

#[derive(Default)]
pub struct TriMesh {
pub points: Vec<nalgebra::Point3<f32>>,
pub triangles: Vec<[u32; 3]>,
}

impl TriMesh {
fn populate_from_gltf(
&mut self,
g_node: gltf::Node,
parent_transform: nalgebra::Matrix4<f32>,
data_buffers: &[Vec<u8>],
) {
let name = g_node.name().unwrap_or("");
let transform = parent_transform * nalgebra::Matrix4::from(g_node.transform().matrix());

for child in g_node.children() {
self.populate_from_gltf(child, transform, data_buffers);
}

let g_mesh = match g_node.mesh() {
Some(mesh) => mesh,
None => return,
};

for (prim_index, g_primitive) in g_mesh.primitives().enumerate() {
if g_primitive.mode() != gltf::mesh::Mode::Triangles {
log::warn!(
"Skipping primitive '{}'[{}] for having mesh mode {:?}",
name,
prim_index,
g_primitive.mode()
);
continue;
}

let reader = g_primitive.reader(|buffer| Some(&data_buffers[buffer.index()]));

// Read the vertices into memory
profiling::scope!("Read data");
let base_vertex = self.points.len() as u32;

match reader.read_indices() {
Some(read) => {
let mut read_u32 = read.into_u32();
let tri_count = read_u32.len() / 3;
for _ in 0..tri_count {
let mut tri = [0u32; 3];
for index in tri.iter_mut() {
*index = base_vertex + read_u32.next().unwrap();
}
self.triangles.push(tri);
}
}
None => {
log::warn!("Missing index buffer for '{name}'");
continue;
}
}

for pos in reader.read_positions().unwrap() {
let point = transform.transform_point(&pos.into());
self.points.push(point);
}
}
}
}

pub fn load(path: &str) -> TriMesh {
use base64::engine::{general_purpose::URL_SAFE as ENCODING_ENGINE, Engine as _};

let gltf::Gltf { document, mut blob } = gltf::Gltf::open(path).unwrap();
// extract buffers
let mut data_buffers = Vec::new();
for buffer in document.buffers() {
let mut data = match buffer.source() {
gltf::buffer::Source::Uri(uri) => {
if let Some(rest) = uri.strip_prefix("data:") {
let (_before, after) = rest.split_once(";base64,").unwrap();
ENCODING_ENGINE.decode(after).unwrap()
} else {
unimplemented!("Unexpected reference to external file: {uri}");
}
}
gltf::buffer::Source::Bin => blob.take().unwrap(),
};
assert!(data.len() >= buffer.length());
while data.len() % 4 != 0 {
data.push(0);
}
data_buffers.push(data);
}

let scene = document.scenes().next().expect("Document has no scenes?");
let mut trimesh = TriMesh::default();
for g_node in scene.nodes() {
trimesh.populate_from_gltf(g_node, nalgebra::Matrix4::identity(), &data_buffers);
}
trimesh
}

0 comments on commit addb2a8

Please sign in to comment.