230 lines
8.2 KiB
Rust
230 lines
8.2 KiB
Rust
use rapier3d::prelude::*;
|
|
use rapier_testbed3d::{KeyCode, Testbed};
|
|
|
|
pub fn init_world(testbed: &mut Testbed) {
|
|
/*
|
|
* World
|
|
*/
|
|
let mut bodies = RigidBodySet::new();
|
|
let mut colliders = ColliderSet::new();
|
|
let mut impulse_joints = ImpulseJointSet::new();
|
|
let multibody_joints = MultibodyJointSet::new();
|
|
|
|
/*
|
|
* Ground.
|
|
*/
|
|
let ground_size = Vector::new(60.0, 0.4, 60.0);
|
|
let nsubdivs = 100;
|
|
|
|
let heights = DMatrix::from_fn(nsubdivs + 1, nsubdivs + 1, |i, j| {
|
|
-(i as f32 * ground_size.x / (nsubdivs as f32) / 2.0).cos()
|
|
- (j as f32 * ground_size.z / (nsubdivs as f32) / 2.0).cos()
|
|
});
|
|
|
|
let collider = ColliderBuilder::heightfield(heights, ground_size)
|
|
.translation(vector![-7.0, 0.0, 0.0])
|
|
.friction(1.0);
|
|
colliders.insert(collider);
|
|
|
|
/*
|
|
* Vehicle we will control manually, simulated using joints.
|
|
* Strongly inspired from https://github.com/h3r2tic/cornell-mcray/blob/main/src/car.rs (MIT/Apache license).
|
|
*/
|
|
const CAR_GROUP: Group = Group::GROUP_1;
|
|
|
|
let wheel_params = [
|
|
vector![0.6874, 0.2783, -0.7802],
|
|
vector![-0.6874, 0.2783, -0.7802],
|
|
vector![0.64, 0.2783, 1.0254],
|
|
vector![-0.64, 0.2783, 1.0254],
|
|
];
|
|
// TODO: lower center of mass?
|
|
// let mut center_of_mass = wheel_params.iter().sum().unwrap() / 4.0;
|
|
// center_of_mass.y = 0.0;
|
|
|
|
let suspension_height = 0.12;
|
|
let max_steering_angle = 35.0f32.to_radians();
|
|
let drive_strength = 1.0;
|
|
let wheel_radius = 0.28;
|
|
let car_position = point![0.0, wheel_radius + suspension_height, 0.0];
|
|
let body_position_in_car_space = vector![0.0, 0.4739, 0.0];
|
|
|
|
let body_position = car_position + body_position_in_car_space;
|
|
|
|
let body_co = ColliderBuilder::cuboid(0.65, 0.3, 0.9)
|
|
.density(100.0)
|
|
.collision_groups(InteractionGroups::new(CAR_GROUP, !CAR_GROUP));
|
|
let body_rb = RigidBodyBuilder::dynamic()
|
|
.position(body_position.into())
|
|
.build();
|
|
let body_handle = bodies.insert(body_rb);
|
|
colliders.insert_with_parent(body_co, body_handle, &mut bodies);
|
|
|
|
let mut steering_joints = vec![];
|
|
let mut motor_joints = vec![];
|
|
|
|
for (wheel_id, wheel_pos_in_car_space) in wheel_params.into_iter().enumerate() {
|
|
let is_front = wheel_id >= 2;
|
|
let wheel_center = car_position + wheel_pos_in_car_space;
|
|
|
|
let axle_mass_props = MassProperties::from_ball(100.0, wheel_radius);
|
|
let axle_rb = RigidBodyBuilder::dynamic()
|
|
.position(wheel_center.into())
|
|
.additional_mass_properties(axle_mass_props);
|
|
let axle_handle = bodies.insert(axle_rb);
|
|
|
|
// This is a fake cylinder collider that we add only because our testbed can
|
|
// only render colliders. Setting it as sensor makes it show up as wireframe.
|
|
let wheel_fake_co = ColliderBuilder::cylinder(wheel_radius / 2.0, wheel_radius)
|
|
.rotation(Vector::z() * std::f32::consts::FRAC_PI_2)
|
|
.sensor(true)
|
|
.density(0.0)
|
|
.collision_groups(InteractionGroups::none());
|
|
|
|
// The actual wheel collider. Simulating the wheel as a ball is interesting as it
|
|
// is mathematically simpler than a cylinder and cheaper to compute for collision-detection.
|
|
let wheel_co = ColliderBuilder::ball(wheel_radius)
|
|
.density(100.0)
|
|
.collision_groups(InteractionGroups::new(CAR_GROUP, !CAR_GROUP))
|
|
.friction(1.0);
|
|
let wheel_rb = RigidBodyBuilder::dynamic().position(wheel_center.into());
|
|
let wheel_handle = bodies.insert(wheel_rb);
|
|
colliders.insert_with_parent(wheel_co, wheel_handle, &mut bodies);
|
|
colliders.insert_with_parent(wheel_fake_co, wheel_handle, &mut bodies);
|
|
|
|
let suspension_attachment_in_body_space =
|
|
wheel_pos_in_car_space - body_position_in_car_space;
|
|
|
|
// Suspension between the body and the axle.
|
|
let mut locked_axes = JointAxesMask::LIN_X
|
|
| JointAxesMask::LIN_Z
|
|
| JointAxesMask::ANG_X
|
|
| JointAxesMask::ANG_Z;
|
|
if !is_front {
|
|
locked_axes |= JointAxesMask::ANG_Y;
|
|
}
|
|
|
|
let mut suspension_joint = GenericJointBuilder::new(locked_axes)
|
|
.limits(JointAxis::LinY, [0.0, suspension_height])
|
|
.motor_position(JointAxis::LinY, 0.0, 1.0e4, 1.0e3)
|
|
.local_anchor1(suspension_attachment_in_body_space.into());
|
|
|
|
if is_front {
|
|
suspension_joint =
|
|
suspension_joint.limits(JointAxis::AngY, [-max_steering_angle, max_steering_angle]);
|
|
}
|
|
|
|
let body_axle_joint_handle =
|
|
impulse_joints.insert(body_handle, axle_handle, suspension_joint, true);
|
|
|
|
// Joint between the axle and the wheel.
|
|
let wheel_joint = RevoluteJointBuilder::new(Vector::x_axis());
|
|
let wheel_joint_handle =
|
|
impulse_joints.insert(axle_handle, wheel_handle, wheel_joint, true);
|
|
|
|
if is_front {
|
|
steering_joints.push(body_axle_joint_handle);
|
|
motor_joints.push(wheel_joint_handle);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Callback to control the wheels motors.
|
|
*/
|
|
testbed.add_callback(move |gfx, physics, _, _| {
|
|
let Some(gfx) = gfx else { return };
|
|
|
|
let mut thrust = 0.0;
|
|
let mut steering = 0.0;
|
|
let mut boost = 1.0;
|
|
|
|
for key in gfx.keys().get_pressed() {
|
|
match *key {
|
|
KeyCode::ArrowRight => {
|
|
steering = -1.0;
|
|
}
|
|
KeyCode::ArrowLeft => {
|
|
steering = 1.0;
|
|
}
|
|
KeyCode::ArrowUp => {
|
|
thrust = -drive_strength;
|
|
}
|
|
KeyCode::ArrowDown => {
|
|
thrust = drive_strength;
|
|
}
|
|
KeyCode::ShiftRight => {
|
|
boost = 1.5;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
if thrust != 0.0 || steering != 0.0 {
|
|
physics.bodies.get_mut(body_handle).unwrap().wake_up(true);
|
|
}
|
|
|
|
// Apply steering to the axles.
|
|
for steering_handle in &steering_joints {
|
|
let steering_joint = physics.impulse_joints.get_mut(*steering_handle).unwrap();
|
|
steering_joint.data.set_motor_position(
|
|
JointAxis::AngY,
|
|
max_steering_angle * steering,
|
|
1.0e4,
|
|
1.0e3,
|
|
);
|
|
}
|
|
|
|
// Apply thrust.
|
|
// Pseudo-differential adjusting speed of engines depending on steering arc
|
|
// Higher values result in more drifty behavior.
|
|
let differential_strength = 0.5;
|
|
let sideways_shift = (max_steering_angle * steering).sin() * differential_strength;
|
|
let speed_diff = if sideways_shift > 0.0 {
|
|
f32::hypot(1.0, sideways_shift)
|
|
} else {
|
|
1.0 / f32::hypot(1.0, sideways_shift)
|
|
};
|
|
|
|
let ms = [1.0 / speed_diff, speed_diff];
|
|
for (motor_handle, &ms) in motor_joints.iter().copied().zip(ms.iter()) {
|
|
let motor_joint = physics.impulse_joints.get_mut(motor_handle).unwrap();
|
|
motor_joint.data.set_motor_velocity(
|
|
JointAxis::AngX,
|
|
-30.0 * thrust * ms * boost,
|
|
1.0e2,
|
|
);
|
|
}
|
|
});
|
|
|
|
/*
|
|
* Create some cubes on the ground.
|
|
*/
|
|
// let num = 8;
|
|
// let rad = 0.1;
|
|
//
|
|
// let shift = rad * 2.0;
|
|
// let centerx = shift * (num / 2) as f32;
|
|
// let centery = rad;
|
|
//
|
|
// for j in 0usize..1 {
|
|
// for k in 0usize..4 {
|
|
// for i in 0..num {
|
|
// let x = i as f32 * shift - centerx;
|
|
// let y = j as f32 * shift + centery;
|
|
// let z = k as f32 * shift + centerx;
|
|
//
|
|
// let rigid_body = RigidBodyBuilder::dynamic().translation(vector![x, y, z]);
|
|
// let handle = bodies.insert(rigid_body);
|
|
// let collider = ColliderBuilder::cuboid(rad, rad, rad);
|
|
// colliders.insert_with_parent(collider, handle, &mut bodies);
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
/*
|
|
* Set up the testbed.
|
|
*/
|
|
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
|
|
testbed.look_at(point!(10.0, 10.0, 10.0), Point::origin());
|
|
}
|