rapier_testbed3d_f64/testbed/
testbed.rs

1//! Testbed struct for building examples.
2
3use kiss3d::color::Color;
4use rapier::dynamics::{
5    ImpulseJointSet, IntegrationParameters, MultibodyJointSet, RigidBodyHandle, RigidBodySet,
6};
7use rapier::geometry::{ColliderHandle, ColliderSet};
8use rapier::pipeline::PhysicsHooks;
9
10#[cfg(feature = "dim3")]
11use {glamx::Vec3, rapier::control::DynamicRayCastVehicleController};
12
13use crate::harness::Harness;
14use crate::physics::PhysicsState;
15use crate::settings::ExampleSettings;
16
17use super::graphics_context::TestbedGraphics;
18use super::state::{TestbedActionFlags, TestbedState};
19
20#[cfg(all(feature = "dim3", feature = "other-backends"))]
21use super::OtherBackends;
22
23#[cfg(all(feature = "dim3", feature = "other-backends"))]
24use super::state::{PHYSX_BACKEND_PATCH_FRICTION, PHYSX_BACKEND_TWO_FRICTION_DIR};
25
26#[cfg(all(feature = "dim3", feature = "other-backends"))]
27use crate::physx_backend::PhysxWorld;
28
29use super::Plugins;
30
31/// An example/demo that can be run in the testbed
32#[derive(Clone)]
33pub struct Example {
34    /// Display name of the example
35    pub name: &'static str,
36    /// Group/category for organizing in the UI (e.g., "Demos", "Joints", "Debug")
37    pub group: &'static str,
38    /// The builder function that initializes the example
39    pub builder: fn(&mut Testbed),
40}
41
42impl Example {
43    /// Create a new example with a group
44    pub fn new(group: &'static str, name: &'static str, builder: fn(&mut Testbed)) -> Self {
45        Self {
46            name,
47            group,
48            builder,
49        }
50    }
51
52    /// Create a new example in the default "Demos" group
53    pub fn demo(name: &'static str, builder: fn(&mut Testbed)) -> Self {
54        Self::new("Demos", name, builder)
55    }
56}
57
58/// Allow constructing Example from a tuple (group, name, builder) for convenience
59impl From<(&'static str, &'static str, fn(&mut Testbed))> for Example {
60    fn from((group, name, builder): (&'static str, &'static str, fn(&mut Testbed))) -> Self {
61        Self::new(group, name, builder)
62    }
63}
64
65/// Type alias for simulation builder functions
66pub type SimulationBuilders = Vec<Example>;
67
68/// The main testbed struct passed to example builders
69pub struct Testbed<'a> {
70    pub graphics: Option<TestbedGraphics<'a>>,
71    pub harness: &'a mut Harness,
72    pub state: &'a mut TestbedState,
73    #[cfg(all(feature = "dim3", feature = "other-backends"))]
74    pub other_backends: &'a mut OtherBackends,
75    pub plugins: &'a mut Plugins,
76}
77
78impl Testbed<'_> {
79    pub fn set_number_of_steps_per_frame(&mut self, nsteps: usize) {
80        self.state.nsteps = nsteps;
81    }
82
83    #[cfg(feature = "dim3")]
84    pub fn set_vehicle_controller(&mut self, controller: DynamicRayCastVehicleController) {
85        self.state.vehicle_controller = Some(controller);
86    }
87
88    pub fn allow_grabbing_behind_ground(&mut self, allow: bool) {
89        self.state.can_grab_behind_ground = allow;
90    }
91
92    pub fn integration_parameters_mut(&mut self) -> &mut IntegrationParameters {
93        &mut self.harness.physics.integration_parameters
94    }
95
96    pub fn physics_state_mut(&mut self) -> &mut PhysicsState {
97        &mut self.harness.physics
98    }
99
100    pub fn harness(&self) -> &Harness {
101        self.harness
102    }
103
104    pub fn harness_mut(&mut self) -> &mut Harness {
105        self.harness
106    }
107
108    pub fn example_settings_mut(&mut self) -> &mut ExampleSettings {
109        &mut self.state.example_settings
110    }
111
112    pub fn set_world(
113        &mut self,
114        bodies: RigidBodySet,
115        colliders: ColliderSet,
116        impulse_joints: ImpulseJointSet,
117        multibody_joints: MultibodyJointSet,
118    ) {
119        self.set_world_with_params(
120            bodies,
121            colliders,
122            impulse_joints,
123            multibody_joints,
124            rapier::math::Vector::Y * -9.81,
125            (),
126        )
127    }
128
129    pub fn set_world_with_params(
130        &mut self,
131        bodies: RigidBodySet,
132        colliders: ColliderSet,
133        impulse_joints: ImpulseJointSet,
134        multibody_joints: MultibodyJointSet,
135        gravity: rapier::math::Vector,
136        hooks: impl PhysicsHooks + 'static,
137    ) {
138        self.harness.set_world_with_params(
139            bodies,
140            colliders,
141            impulse_joints,
142            multibody_joints,
143            self.state.broad_phase_type,
144            gravity,
145            hooks,
146        );
147
148        self.state
149            .action_flags
150            .set(TestbedActionFlags::RESET_WORLD_GRAPHICS, true);
151
152        #[cfg(feature = "dim3")]
153        {
154            self.state.vehicle_controller = None;
155        }
156
157        #[cfg(all(feature = "dim3", feature = "other-backends"))]
158        {
159            if self.state.selected_backend == PHYSX_BACKEND_PATCH_FRICTION
160                || self.state.selected_backend == PHYSX_BACKEND_TWO_FRICTION_DIR
161            {
162                self.other_backends.physx = Some(PhysxWorld::from_rapier(
163                    self.harness.physics.gravity,
164                    &self.harness.physics.integration_parameters,
165                    &self.harness.physics.bodies,
166                    &self.harness.physics.colliders,
167                    &self.harness.physics.impulse_joints,
168                    &self.harness.physics.multibody_joints,
169                    self.state.selected_backend == PHYSX_BACKEND_TWO_FRICTION_DIR,
170                    self.harness.state.num_threads(),
171                ));
172            }
173        }
174    }
175
176    pub fn set_graphics_shift(&mut self, shift: rapier::math::Vector) {
177        if !self.state.camera_locked
178            && let Some(graphics) = &mut self.graphics
179        {
180            graphics.graphics.gfx_shift = shift;
181        }
182    }
183
184    #[cfg(feature = "dim2")]
185    pub fn look_at(&mut self, at: glamx::Vec2, zoom: f32) {
186        if !self.state.camera_locked
187            && let Some(graphics) = &mut self.graphics
188        {
189            graphics.camera.set_at(at);
190            graphics.camera.set_zoom(zoom);
191        }
192    }
193
194    #[cfg(feature = "dim3")]
195    pub fn look_at(&mut self, eye: Vec3, at: Vec3) {
196        if !self.state.camera_locked
197            && let Some(graphics) = &mut self.graphics
198        {
199            graphics.camera.look_at(eye, at);
200        }
201    }
202
203    pub fn set_initial_body_color(&mut self, body: RigidBodyHandle, color: Color) {
204        if let Some(graphics) = &mut self.graphics {
205            graphics.graphics.set_initial_body_color(body, color);
206        }
207    }
208
209    pub fn set_initial_collider_color(&mut self, collider: ColliderHandle, color: Color) {
210        if let Some(graphics) = &mut self.graphics {
211            graphics
212                .graphics
213                .set_initial_collider_color(collider, color);
214        }
215    }
216
217    pub fn set_body_wireframe(&mut self, body: RigidBodyHandle, wireframe_enabled: bool) {
218        if let Some(graphics) = &mut self.graphics {
219            graphics
220                .graphics
221                .set_body_wireframe(body, wireframe_enabled);
222        }
223    }
224
225    pub fn add_callback<
226        F: FnMut(
227                Option<&mut TestbedGraphics>,
228                &mut PhysicsState,
229                &crate::physics::PhysicsEvents,
230                &crate::harness::RunState,
231            ) + 'static,
232    >(
233        &mut self,
234        callback: F,
235    ) {
236        self.harness.add_callback(callback);
237    }
238
239    pub fn add_plugin(&mut self, mut plugin: impl crate::plugin::TestbedPlugin + 'static) {
240        plugin.init_plugin();
241        self.plugins.0.push(Box::new(plugin));
242    }
243}