feat: add support for Voxels collider (#823)

* feat: start adding voxels support and some additional testbed demo settings

* feat: add support for parry’s new Voxels collider shape

* fix voxels demos

* feat: support rectangular voxels and additional voxels initialization

* chore: switch to parry 0.20

* chore: fix cargo doc

* Fix testbed build
This commit is contained in:
Sébastien Crozet
2025-04-24 12:11:53 +02:00
committed by GitHub
parent 1c67c5e7f2
commit e44f636249
27 changed files with 891 additions and 223 deletions

View File

@@ -7,6 +7,9 @@ use bevy::input::mouse::MouseWheel;
use bevy::prelude::*;
use bevy::render::camera::Camera;
#[cfg(target_os = "macos")]
const LINE_TO_PIXEL_RATIO: f32 = 0.0005;
#[cfg(not(target_os = "macos"))]
const LINE_TO_PIXEL_RATIO: f32 = 0.1;
#[derive(Component, PartialEq, Debug, Clone, serde::Serialize, serde::Deserialize)]

View File

@@ -1,4 +1,5 @@
#![allow(clippy::too_many_arguments)]
extern crate nalgebra as na;
pub use crate::graphics::{BevyMaterial, GraphicsManager};

View File

@@ -17,7 +17,7 @@ use rapier::math::{Isometry, Real, Vector};
use crate::graphics::{BevyMaterial, InstancedMaterials, SELECTED_OBJECT_MATERIAL_KEY};
#[cfg(feature = "dim2")]
use {
na::{Point2, Vector2},
na::{vector, Point2, Vector2},
rapier::geometry::{Ball, Cuboid},
};
@@ -439,6 +439,26 @@ fn generate_collider_mesh(co_shape: &dyn Shape) -> Option<Mesh> {
.collect();
bevy_mesh((vertices, trimesh.indices().to_vec()))
}
ShapeType::Voxels => {
let mut vtx = vec![];
let mut idx = vec![];
let voxels = co_shape.as_voxels().unwrap();
let sz = voxels.voxel_size() / 2.0;
for (_, center, data) in voxels.centers() {
if !data.is_empty() {
let bid = vtx.len() as u32;
let center = point![center.x, center.y, 0.0];
vtx.push(center + vector![sz.x, sz.y, 0.0]);
vtx.push(center + vector![-sz.x, sz.y, 0.0]);
vtx.push(center + vector![-sz.x, -sz.y, 0.0]);
vtx.push(center + vector![sz.x, -sz.y, 0.0]);
idx.push([bid, bid + 1, bid + 2]);
idx.push([bid + 2, bid + 3, bid]);
}
}
bevy_mesh((vtx, idx))
}
ShapeType::Polyline => {
let polyline = co_shape.as_polyline().unwrap();
bevy_polyline((
@@ -495,6 +515,10 @@ fn generate_collider_mesh(co_shape: &dyn Shape) -> Option<Mesh> {
let poly = co_shape.as_round_convex_polyhedron().unwrap();
bevy_mesh(poly.inner_shape.to_trimesh())
}
ShapeType::Voxels => {
let voxels = co_shape.as_voxels().unwrap();
bevy_mesh(voxels.to_trimesh())
}
_ => return None,
};

View File

@@ -1,8 +1,9 @@
use std::collections::HashMap;
use indexmap::IndexMap;
use std::ops::RangeInclusive;
#[derive(Clone, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
pub enum SettingValue {
Label(String),
U32 {
value: u32,
range: RangeInclusive<u32>,
@@ -11,11 +12,18 @@ pub enum SettingValue {
value: f32,
range: RangeInclusive<f32>,
},
Bool {
value: bool,
},
String {
value: usize,
range: Vec<String>,
},
}
#[derive(Default, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ExampleSettings {
values: HashMap<String, SettingValue>,
values: IndexMap<String, SettingValue>,
}
impl ExampleSettings {
@@ -35,6 +43,27 @@ impl ExampleSettings {
self.values.iter_mut()
}
pub fn set_bool(&mut self, key: &str, value: bool) {
self.values
.insert(key.to_string(), SettingValue::Bool { value });
}
pub fn get_or_set_bool(&mut self, key: &'static str, default: bool) -> bool {
let to_insert = SettingValue::Bool { value: default };
let entry = self
.values
.entry(key.to_string())
.or_insert(to_insert.clone());
match entry {
SettingValue::Bool { value } => *value,
_ => {
// The entry doesnt have the right type. Overwrite with the new value.
*entry = to_insert;
default
}
}
}
pub fn set_u32(&mut self, key: &str, value: u32, range: RangeInclusive<u32>) {
self.values
.insert(key.to_string(), SettingValue::U32 { value, range });
@@ -90,6 +119,47 @@ impl ExampleSettings {
}
}
pub fn set_string(&mut self, key: &str, selected: usize, range: Vec<String>) {
self.values.insert(
key.to_string(),
SettingValue::String {
value: selected,
range,
},
);
}
pub fn get_or_set_string(
&mut self,
key: &'static str,
default: usize,
range: Vec<String>,
) -> usize {
let to_insert = SettingValue::String {
value: default,
range,
};
let entry = self
.values
.entry(key.to_string())
.or_insert(to_insert.clone());
match entry {
SettingValue::String { value, .. } => *value,
_ => {
// The entry doesnt have the right type. Overwrite with the new value.
*entry = to_insert;
default
}
}
}
pub fn get_bool(&self, key: &'static str) -> Option<bool> {
match self.values.get(key)? {
SettingValue::Bool { value } => Some(*value),
_ => None,
}
}
pub fn get_u32(&self, key: &'static str) -> Option<u32> {
match self.values.get(key)? {
SettingValue::U32 { value, .. } => Some(*value),
@@ -103,4 +173,23 @@ impl ExampleSettings {
_ => None,
}
}
pub fn get_string_id(&self, key: &'static str) -> Option<usize> {
match self.values.get(key)? {
SettingValue::String { value, .. } => Some(*value),
_ => None,
}
}
pub fn get_string(&self, key: &'static str) -> Option<&str> {
match self.values.get(key)? {
SettingValue::String { value, range } => Some(&range[*value]),
_ => None,
}
}
pub fn set_label(&mut self, key: &'static str, value: impl Into<String>) {
self.values
.insert(key.to_string(), SettingValue::Label(value.into()));
}
}

View File

@@ -1014,18 +1014,20 @@ fn draw_contacts(_nf: &NarrowPhase, _colliders: &ColliderSet) {
#[cfg(feature = "dim3")]
fn setup_graphics_environment(mut commands: Commands) {
commands.insert_resource(AmbientLight {
brightness: 100.0,
brightness: 200.0,
..Default::default()
});
commands.spawn((
DirectionalLight {
shadows_enabled: false,
// shadows_enabled: true,
..Default::default()
},
Transform {
translation: Vec3::new(10.0, 2.0, 10.0),
rotation: Quat::from_rotation_x(-std::f32::consts::FRAC_PI_4),
translation: Vec3::new(-100.0, 200.0, -100.0),
rotation: Quat::from_rotation_x(
-(std::f32::consts::FRAC_PI_4 + std::f32::consts::FRAC_PI_6),
),
..Default::default()
},
));

View File

@@ -439,6 +439,9 @@ fn example_settings_ui(ui_context: &mut EguiContexts, state: &mut TestbedState)
for (name, value) in state.example_settings.iter_mut() {
let prev_value = value.clone();
match value {
SettingValue::Label(value) => {
ui.label(format!("{}: {}", name, value));
}
SettingValue::F32 { value, range } => {
ui.add(Slider::new(value, range.clone()).text(name));
}
@@ -454,6 +457,19 @@ fn example_settings_ui(ui_context: &mut EguiContexts, state: &mut TestbedState)
ui.add(Slider::new(value, range.clone()).text(name));
});
}
SettingValue::Bool { value } => {
ui.checkbox(value, name);
}
SettingValue::String { value, range } => {
ComboBox::from_label(name)
.width(150.0)
.selected_text(&range[*value])
.show_ui(ui, |ui| {
for (id, name) in range.iter().enumerate() {
ui.selectable_value(value, id, name);
}
});
}
}
any_changed = any_changed || *value != prev_value;