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, pub contact_normal: Option, } 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>, mut materials: ResMut>, ) { 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>, mut materials: ResMut>, 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>, materials: &mut ResMut>, 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::(); app.register_type::(); app.add_systems(Startup, spawn_bike); } }