Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support TriMesh collision shapes loaded from gltf #64

Merged
merged 2 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
27 changes: 23 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 Expand Up @@ -65,4 +83,5 @@ pub struct Object {
#[derive(serde::Deserialize)]
pub struct Engine {
pub shader_path: String,
pub data_path: String,
}
53 changes: 42 additions & 11 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 @@ -195,6 +196,7 @@ pub struct Engine {
track_hot_reloads: bool,
workers: Vec<choir::WorkerHandle>,
choir: Arc<choir::Choir>,
data_path: String,
}

impl ops::Index<JointHandle> for Engine {
Expand Down Expand Up @@ -273,7 +275,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 @@ -325,6 +327,7 @@ impl Engine {
track_hot_reloads: false,
workers,
choir,
data_path: config.data_path.clone(),
}
}

Expand Down Expand Up @@ -616,7 +619,7 @@ impl Engine {
let mut visuals = Vec::new();
for visual in config.visuals.iter() {
let (model, task) = self.asset_hub.models.load(
format!("data/{}", visual.model),
format!("{}/{}", self.data_path, visual.model),
blade_render::model::Meta {
generate_tangents: true,
},
Expand All @@ -639,26 +642,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!("{}/{}", self.data_path, 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 Expand Up @@ -729,7 +760,7 @@ impl Engine {
if path.is_empty() {
self.environment_map = None;
} else {
let full = format!("data/{}", path);
let full = format!("{}/{}", self.data_path, path);
let (handle, task) = self.asset_hub.textures.load(
full,
blade_render::texture::Meta {
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
}
Loading