avian3d-sandbox/src/bike.rs
2025-04-06 21:41:51 -07:00

245 lines
6.2 KiB
Rust

use avian3d::{
math::{Scalar, FRAC_PI_2},
prelude::*,
};
use bevy::prelude::*;
use crate::physics::CatControllerState;
pub const SPRING_CONSTANT: Scalar = 60.0;
pub const DAMPING_CONSTANT: Scalar = 10.0;
pub const WHEEL_RADIUS: Scalar = 0.4;
pub const REST_DISTANCE: Scalar = 1.5 + WHEEL_RADIUS;
pub const FRICTION_COEFF: Scalar = 0.9;
pub const FRONT_ATTACH: Vec3 = Vec3::new(0.0, 0.0, -0.5);
pub const REAR_ATTACH: Vec3 = Vec3::new(0.0, 0.0, 0.5);
#[derive(Component)]
pub struct CyberBikeBody;
#[derive(Component, Clone, Copy, Debug)]
pub enum CyberWheel {
Front,
Rear,
}
#[derive(Component, Clone, Copy, Debug, Reflect, Default)]
#[reflect(Component)]
pub struct WheelState {
pub displacement: Scalar,
pub force_normal: Scalar,
pub sliding: bool,
pub contact_point: Option<Vec3>,
pub contact_normal: Option<Vec3>,
}
impl WheelState {
pub fn reset(&mut self) {
*self = Self::default();
}
}
#[derive(Component, Clone, Copy, Debug, Reflect)]
#[reflect(Component)]
#[require(WheelState)]
pub struct WheelConfig {
pub attach: Vec3,
pub rest_dist: Scalar,
pub konstant: Scalar,
pub damping: Scalar,
pub friction: Scalar,
pub radius: Scalar,
}
impl WheelConfig {
fn new(
attach: Vec3,
rest_dist: Scalar,
konstant: Scalar,
damping: Scalar,
friction: Scalar,
radius: Scalar,
) -> Self {
WheelConfig {
attach,
rest_dist,
konstant,
damping,
friction,
radius,
}
}
}
fn spawn_bike(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let pos = Vec3::new(0.0, 5.0, 0.0);
let xform = Transform::from_translation(pos); //.with_rotation(Quat::from_rotation_z(0.0));
let body_collider =
Collider::capsule_endpoints(0.5, Vec3::new(0.0, 0.0, -0.65), Vec3::new(0.0, 0.0, 0.8));
commands
.spawn((xform, Visibility::default()))
.insert((
Name::new("body"),
RigidBody::Dynamic,
body_collider,
CollisionLayers::from_bits(1, 1),
SleepingDisabled,
CyberBikeBody,
CatControllerState::default(),
ColliderDensity(1.2),
AngularDamping(0.2),
LinearDamping(0.1),
ExternalForce::ZERO.with_persistence(false),
ExternalTorque::ZERO.with_persistence(false),
))
.with_children(|builder| {
let color = Color::srgb(0.7, 0.05, 0.7);
let mut xform = Transform::default();
xform.rotate_x(FRAC_PI_2);
let pbr_bundle = (
Mesh3d(meshes.add(Capsule3d::new(0.5, 1.45))),
xform,
MeshMaterial3d(materials.add(color)),
);
builder.spawn(pbr_bundle);
spawn_wheels(builder, meshes, materials, builder.parent_entity());
});
}
fn spawn_wheels(
commands: &mut ChildBuilder,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
body: Entity,
) {
let mesh: Mesh = Sphere::new(WHEEL_RADIUS).into();
let front_rake = Vec3::new(0.0, -1.0, -0.9).normalize(); // about 30 degrees
let front_wheel_pos = FRONT_ATTACH + (front_rake * REST_DISTANCE);
wheel_caster(
commands,
FRONT_ATTACH,
Dir3::new_unchecked(front_rake),
body,
REST_DISTANCE,
CyberWheel::Front,
);
wheel_mesh(
commands,
&mut meshes,
&mut materials,
front_wheel_pos,
mesh.clone(),
WheelConfig::new(
FRONT_ATTACH,
REST_DISTANCE,
SPRING_CONSTANT,
DAMPING_CONSTANT,
FRICTION_COEFF,
WHEEL_RADIUS,
),
CyberWheel::Front,
);
let rear_rake = Vec3::new(0.0, -1.0, 0.9).normalize();
let rear_wheel_pos = REAR_ATTACH + (rear_rake * REST_DISTANCE);
wheel_caster(
commands,
REAR_ATTACH,
Dir3::new_unchecked(rear_rake),
body,
REST_DISTANCE,
CyberWheel::Rear,
);
wheel_mesh(
commands,
&mut meshes,
&mut materials,
rear_wheel_pos,
mesh,
WheelConfig::new(
REAR_ATTACH,
REST_DISTANCE,
SPRING_CONSTANT,
DAMPING_CONSTANT,
FRICTION_COEFF,
WHEEL_RADIUS,
),
CyberWheel::Rear,
);
}
//-************************************************************************
// helper fns for the wheels
//-************************************************************************
fn wheel_caster(
commands: &mut ChildBuilder,
origin: Vec3,
direction: Dir3,
parent: Entity,
rest_dist: Scalar,
wheel: CyberWheel,
) {
let caster = RayCaster::new(origin, direction)
.with_max_distance(rest_dist)
.with_max_hits(1)
.with_query_filter(SpatialQueryFilter::from_excluded_entities([parent]));
commands.spawn((caster, wheel));
}
fn wheel_mesh(
commands: &mut ChildBuilder,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<StandardMaterial>>,
position: Vec3,
tire_mesh: Mesh,
config: WheelConfig,
wheel: CyberWheel,
) {
let wheel_material = &StandardMaterial {
base_color: Color::srgb(0.01, 0.01, 0.01),
alpha_mode: AlphaMode::Opaque,
perceptual_roughness: 0.5,
..Default::default()
};
let xform = Transform::from_translation(position);
let name = match wheel {
CyberWheel::Front => "front tire",
CyberWheel::Rear => "rear tire",
};
commands.spawn((
Name::new(name),
config,
Mesh3d(meshes.add(tire_mesh)),
MeshMaterial3d(materials.add(wheel_material.clone())),
xform,
Collider::sphere(WHEEL_RADIUS),
ColliderDensity(0.5),
CollisionLayers::NONE,
TransformInterpolation,
wheel,
));
}
pub struct CyberBikePlugin;
impl Plugin for CyberBikePlugin {
fn build(&self, app: &mut App) {
app.register_type::<WheelConfig>();
app.register_type::<WheelState>();
app.add_systems(Startup, spawn_bike);
}
}