Compare commits
2 commits
6b9a879927
...
3d0d86f24c
Author | SHA1 | Date | |
---|---|---|---|
|
3d0d86f24c | ||
|
4d94f2bfa4 |
16 changed files with 2516 additions and 1560 deletions
3343
Cargo.lock
generated
3343
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
30
Cargo.toml
30
Cargo.toml
|
@ -6,32 +6,22 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
# bevy_polyline = "0.4"
|
# bevy_polyline = "0.4"
|
||||||
noise = { git = "https://github.com/Razaekel/noise-rs" }
|
noise = "0.9"
|
||||||
hexasphere = "8"
|
hexasphere = "14"
|
||||||
wgpu = "0.15"
|
wgpu = "0.20"
|
||||||
bevy-inspector-egui = "0.18"
|
# bevy-inspector-egui = "0.18"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
inspector = []
|
inspector = []
|
||||||
|
|
||||||
[dependencies.bevy]
|
[dependencies.bevy]
|
||||||
version = "0.10"
|
version = "0.14"
|
||||||
default-features = false
|
default-features = true
|
||||||
features = [
|
features = ["bevy_dev_tools"]
|
||||||
"bevy_gilrs",
|
|
||||||
"bevy_winit",
|
|
||||||
"png",
|
|
||||||
"hdr",
|
|
||||||
"x11",
|
|
||||||
"bevy_ui",
|
|
||||||
"bevy_text",
|
|
||||||
"bevy_gltf",
|
|
||||||
"bevy_sprite",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies.bevy_rapier3d]
|
[dependencies.avian3d]
|
||||||
features = ["debug-render-3d"]
|
default-features = true
|
||||||
version = "0.21"
|
version = "0.1"
|
||||||
|
|
||||||
# Maybe also enable only a small amount of optimization for our code:
|
# Maybe also enable only a small amount of optimization for our code:
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::{Component, ReflectResource, Resource, Vec3},
|
prelude::{Component, ReflectResource, Resource},
|
||||||
reflect::Reflect,
|
reflect::Reflect,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,21 +26,6 @@ impl ActionDebugInstant {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Component)]
|
|
||||||
pub(super) struct Tunneling {
|
|
||||||
pub frames: usize,
|
|
||||||
pub dir: Vec3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Tunneling {
|
|
||||||
fn default() -> Self {
|
|
||||||
Tunneling {
|
|
||||||
frames: 15,
|
|
||||||
dir: Vec3::ZERO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Resource, Reflect)]
|
#[derive(Debug, Resource, Reflect)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
pub struct MovementSettings {
|
pub struct MovementSettings {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
use avian3d::prelude::{PhysicsPlugins, SubstepCount};
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
app::Update,
|
||||||
diagnostic::FrameTimeDiagnosticsPlugin,
|
diagnostic::FrameTimeDiagnosticsPlugin,
|
||||||
ecs::reflect::ReflectResource,
|
ecs::reflect::ReflectResource,
|
||||||
prelude::{App, IntoSystemConfigs, Plugin, Resource},
|
prelude::{App, IntoSystemConfigs, Plugin, Resource},
|
||||||
reflect::Reflect,
|
reflect::Reflect,
|
||||||
};
|
};
|
||||||
use bevy_rapier3d::prelude::{NoUserData, RapierPhysicsPlugin};
|
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
@ -28,20 +29,14 @@ impl Plugin for CyberActionPlugin {
|
||||||
.init_resource::<CyberLean>()
|
.init_resource::<CyberLean>()
|
||||||
.register_type::<CyberLean>()
|
.register_type::<CyberLean>()
|
||||||
.register_type::<CatControllerSettings>()
|
.register_type::<CatControllerSettings>()
|
||||||
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
|
.add_plugins((PhysicsPlugins::default(), FrameTimeDiagnosticsPlugin))
|
||||||
.add_startup_system(timestep_setup)
|
.insert_resource(SubstepCount(12))
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
|
Update,
|
||||||
(
|
(
|
||||||
gravity,
|
gravity,
|
||||||
cyber_lean,
|
(cyber_lean, suspension, falling_cat, input_forces).after(clear_forces),
|
||||||
falling_cat,
|
),
|
||||||
input_forces,
|
|
||||||
drag,
|
|
||||||
tunnel_out,
|
|
||||||
surface_fix,
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
use std::f32::consts::{FRAC_PI_3, FRAC_PI_4};
|
use std::f32::consts::{FRAC_PI_3, FRAC_PI_4};
|
||||||
|
|
||||||
use bevy::prelude::{
|
use avian3d::{
|
||||||
Commands, Entity, Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without,
|
dynamics::solver::xpbd::AngularConstraint,
|
||||||
};
|
prelude::{
|
||||||
use bevy_rapier3d::prelude::{
|
ColliderMassProperties, ExternalForce, ExternalTorque, Gravity, LinearVelocity,
|
||||||
CollisionGroups, ExternalForce, Group, MultibodyJoint, QueryFilter, RapierConfiguration,
|
PrismaticJoint, RigidBodyQuery,
|
||||||
RapierContext, ReadMassProperties, TimestepMode, Velocity,
|
},
|
||||||
};
|
};
|
||||||
|
use bevy::prelude::{Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without};
|
||||||
|
|
||||||
#[cfg(feature = "inspector")]
|
use super::{CatControllerSettings, CatControllerState, CyberLean, MovementSettings};
|
||||||
use super::ActionDebugInstant;
|
|
||||||
use super::{CatControllerSettings, CatControllerState, CyberLean, MovementSettings, Tunneling};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bike::{CyberBikeBody, CyberSteering, CyberWheel, WheelConfig, BIKE_WHEEL_COLLISION_GROUP},
|
bike::{CyberBikeBody, CyberFork, CyberSpring, CyberSteering, CyberWheel, WheelConfig},
|
||||||
input::InputState,
|
input::InputState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,46 +35,56 @@ fn rotate_point(pt: &Vec3, rot: &Quat) -> Vec3 {
|
||||||
-Vec3::from_array([rot_qpt.x, rot_qpt.y, rot_qpt.z])
|
-Vec3::from_array([rot_qpt.x, rot_qpt.y, rot_qpt.z])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn timestep_setup(mut config: ResMut<RapierConfiguration>) {
|
|
||||||
let ts = TimestepMode::Fixed {
|
|
||||||
dt: 1.0 / 60.0,
|
|
||||||
substeps: 2,
|
|
||||||
};
|
|
||||||
config.timestep_mode = ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The gravity vector points from the cyberbike to the center of the planet.
|
/// The gravity vector points from the cyberbike to the center of the planet.
|
||||||
pub(super) fn gravity(
|
pub(super) fn gravity(
|
||||||
mut query: Query<(&Transform, &mut ExternalForce), With<CyberBikeBody>>,
|
xform: Query<&Transform, With<CyberBikeBody>>,
|
||||||
|
|
||||||
settings: Res<MovementSettings>,
|
settings: Res<MovementSettings>,
|
||||||
mut rapier_config: ResMut<RapierConfiguration>,
|
mut gravity: ResMut<Gravity>,
|
||||||
#[cfg(feature = "inspector")] mut debug_instant: ResMut<ActionDebugInstant>,
|
|
||||||
) {
|
) {
|
||||||
let (xform, mut forces) = query.single_mut();
|
let xform = xform.single();
|
||||||
|
*gravity = Gravity(xform.translation.normalize() * -settings.gravity);
|
||||||
|
|
||||||
#[cfg(feature = "inspectorb")]
|
// clear all external forces
|
||||||
{
|
}
|
||||||
if debug_instant.elapsed().as_millis() > 6000 {
|
|
||||||
dbg!(&forces);
|
pub(super) fn clear_forces(mut forces: Query<(&mut ExternalForce, &mut ExternalTorque)>) {
|
||||||
debug_instant.reset();
|
for (mut force, mut torque) in forces.iter_mut() {
|
||||||
}
|
force.clear();
|
||||||
|
torque.clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rapier_config.gravity = xform.translation.normalize() * -settings.gravity;
|
pub(super) fn suspension(
|
||||||
forces.force = Vec3::ZERO;
|
movment_settings: Res<MovementSettings>,
|
||||||
forces.torque = Vec3::ZERO;
|
wheel_config: Res<WheelConfig>,
|
||||||
|
mass: Query<&ColliderMassProperties, With<CyberBikeBody>>,
|
||||||
|
mut axels: Query<(&mut ExternalForce, &CyberSpring, &Transform)>,
|
||||||
|
) {
|
||||||
|
let mass = if let Ok(mass) = mass.get_single() {
|
||||||
|
mass.mass.0
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let gravity = movment_settings.gravity;
|
||||||
|
let mag = -wheel_config.stiffness * mass * gravity;
|
||||||
|
for (mut force, spring, xform) in axels.iter_mut() {
|
||||||
|
let spring = mag * spring.0;
|
||||||
|
let spring = xform.translation + spring;
|
||||||
|
let _ = force.apply_force(spring);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The desired lean angle, given steering input and speed.
|
/// The desired lean angle, given steering input and speed.
|
||||||
pub(super) fn cyber_lean(
|
pub(super) fn cyber_lean(
|
||||||
bike_state: Query<(&Velocity, &Transform), With<CyberBikeBody>>,
|
bike_state: Query<(&LinearVelocity, &Transform), With<CyberBikeBody>>,
|
||||||
wheels: Query<&Transform, With<CyberWheel>>,
|
wheels: Query<&Transform, With<CyberWheel>>,
|
||||||
input: Res<InputState>,
|
input: Res<InputState>,
|
||||||
gravity_settings: Res<MovementSettings>,
|
gravity_settings: Res<MovementSettings>,
|
||||||
mut lean: ResMut<CyberLean>,
|
mut lean: ResMut<CyberLean>,
|
||||||
) {
|
) {
|
||||||
let (velocity, xform) = bike_state.single();
|
let (velocity, xform) = bike_state.single();
|
||||||
let vel = velocity.linvel.dot(xform.forward());
|
let vel = velocity.dot(*xform.forward());
|
||||||
let v_squared = vel.powi(2);
|
let v_squared = vel.powi(2);
|
||||||
let steering_angle = yaw_to_angle(input.yaw);
|
let steering_angle = yaw_to_angle(input.yaw);
|
||||||
let wheels: Vec<_> = wheels.iter().map(|w| w.translation).collect();
|
let wheels: Vec<_> = wheels.iter().map(|w| w.translation).collect();
|
||||||
|
@ -94,20 +103,20 @@ pub(super) fn cyber_lean(
|
||||||
|
|
||||||
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
|
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
|
||||||
pub(super) fn falling_cat(
|
pub(super) fn falling_cat(
|
||||||
mut bike_query: Query<(&Transform, &mut ExternalForce, &mut CatControllerState)>,
|
mut bike_query: Query<(&Transform, &mut ExternalTorque, &mut CatControllerState)>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
settings: Res<CatControllerSettings>,
|
settings: Res<CatControllerSettings>,
|
||||||
lean: Res<CyberLean>,
|
lean: Res<CyberLean>,
|
||||||
) {
|
) {
|
||||||
let (xform, mut forces, mut control_vars) = bike_query.single_mut();
|
let (xform, mut torque, mut control_vars) = bike_query.single_mut();
|
||||||
let world_up = xform.translation.normalize();
|
let world_up = xform.translation.normalize();
|
||||||
let rot = Quat::from_axis_angle(xform.back(), lean.lean);
|
let rot = Quat::from_axis_angle(*xform.back(), lean.lean);
|
||||||
let target_up = rotate_point(&world_up, &rot).normalize();
|
let target_up = rotate_point(&world_up, &rot).normalize();
|
||||||
|
|
||||||
let bike_right = xform.right();
|
let bike_right = xform.right();
|
||||||
|
|
||||||
let roll_error = bike_right.dot(target_up);
|
let roll_error = bike_right.dot(target_up);
|
||||||
let pitch_error = world_up.dot(xform.back());
|
let pitch_error = world_up.dot(*xform.back());
|
||||||
|
|
||||||
// only try to correct roll if we're not totally vertical
|
// only try to correct roll if we're not totally vertical
|
||||||
if pitch_error.abs() < 0.95 {
|
if pitch_error.abs() < 0.95 {
|
||||||
|
@ -115,7 +124,7 @@ pub(super) fn falling_cat(
|
||||||
let mag =
|
let mag =
|
||||||
(settings.kp * roll_error) + (settings.ki * integral) + (settings.kd * derivative);
|
(settings.kp * roll_error) + (settings.ki * integral) + (settings.kd * derivative);
|
||||||
if mag.is_finite() {
|
if mag.is_finite() {
|
||||||
forces.torque += xform.back() * mag;
|
torque.apply_torque(*xform.back() * mag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,20 +133,31 @@ pub(super) fn falling_cat(
|
||||||
pub(super) fn input_forces(
|
pub(super) fn input_forces(
|
||||||
settings: Res<MovementSettings>,
|
settings: Res<MovementSettings>,
|
||||||
input: Res<InputState>,
|
input: Res<InputState>,
|
||||||
mut braking_query: Query<&mut MultibodyJoint, (Without<CyberSteering>, With<CyberWheel>)>,
|
time: Res<Time>,
|
||||||
|
fork: Query<&PrismaticJoint, With<CyberFork>>,
|
||||||
|
mut axle: Query<(RigidBodyQuery, &Transform), With<CyberSteering>>,
|
||||||
|
mut braking_query: Query<&mut ExternalTorque, With<CyberWheel>>,
|
||||||
mut body_query: Query<
|
mut body_query: Query<
|
||||||
(&Transform, &mut ExternalForce),
|
(
|
||||||
|
RigidBodyQuery,
|
||||||
|
&Transform,
|
||||||
|
&ColliderMassProperties,
|
||||||
|
&mut ExternalForce,
|
||||||
|
),
|
||||||
(With<CyberBikeBody>, Without<CyberSteering>),
|
(With<CyberBikeBody>, Without<CyberSteering>),
|
||||||
>,
|
>,
|
||||||
mut steering_query: Query<&mut MultibodyJoint, With<CyberSteering>>,
|
|
||||||
) {
|
) {
|
||||||
let (xform, mut forces) = body_query.single_mut();
|
let Ok((mut bike, xform, mass, mut forces)) = body_query.get_single_mut() else {
|
||||||
|
bevy::log::warn!("no bike body found");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let dt = time.delta_seconds();
|
||||||
|
|
||||||
// thrust
|
// thrust
|
||||||
let thrust = xform.forward() * input.throttle * settings.accel;
|
let thrust = xform.forward() * input.throttle * settings.accel;
|
||||||
let point = xform.translation + xform.back();
|
let point = xform.translation + *xform.back();
|
||||||
let force = ExternalForce::at_point(thrust, point, xform.translation);
|
|
||||||
*forces += force;
|
let _ = *forces.apply_force_at_point(dt * thrust, point, mass.center_of_mass.0);
|
||||||
|
|
||||||
// brake + thrust
|
// brake + thrust
|
||||||
for mut motor in braking_query.iter_mut() {
|
for mut motor in braking_query.iter_mut() {
|
||||||
|
@ -147,99 +167,21 @@ pub(super) fn input_forces(
|
||||||
input.throttle * settings.accel
|
input.throttle * settings.accel
|
||||||
};
|
};
|
||||||
let speed = if input.brake { 0.0 } else { -70.0 };
|
let speed = if input.brake { 0.0 } else { -70.0 };
|
||||||
motor.data = (*motor
|
let target = dt * factor * speed;
|
||||||
.data
|
let torque = target * Vec3::X;
|
||||||
.as_revolute_mut()
|
motor.apply_torque(torque);
|
||||||
.unwrap()
|
|
||||||
.set_motor_max_force(factor)
|
|
||||||
.set_motor_velocity(speed, factor))
|
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// steering
|
// steering
|
||||||
let angle = yaw_to_angle(input.yaw);
|
let _angle = yaw_to_angle(input.yaw);
|
||||||
let mut steering = steering_query.single_mut();
|
let (mut axle, xform) = axle.single_mut();
|
||||||
steering.data = (*steering
|
|
||||||
.data
|
let cur_rot = xform.rotation;
|
||||||
.as_revolute_mut()
|
let fork = fork.single();
|
||||||
.unwrap()
|
let axis = fork.free_axis.normalize();
|
||||||
.set_motor_position(-angle, 100.0, 0.5))
|
let new = Quat::from_axis_angle(axis, _angle);
|
||||||
.into();
|
let diff = (new - cur_rot).to_scaled_axis();
|
||||||
}
|
let mut compliance = 1.0;
|
||||||
|
|
||||||
/// Don't let the wheels get stuck underneat the planet
|
fork.align_orientation(&mut axle, &mut bike, diff, &mut compliance, 1.0, dt);
|
||||||
pub(super) fn surface_fix(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut wheel_query: Query<
|
|
||||||
(Entity, &Transform, &mut CollisionGroups),
|
|
||||||
(With<CyberWheel>, Without<Tunneling>),
|
|
||||||
>,
|
|
||||||
span_query: Query<&Transform, With<CyberWheel>>,
|
|
||||||
config: Res<WheelConfig>,
|
|
||||||
context: Res<RapierContext>,
|
|
||||||
) {
|
|
||||||
let mut wheels = Vec::new();
|
|
||||||
for xform in span_query.iter() {
|
|
||||||
wheels.push(xform);
|
|
||||||
}
|
|
||||||
let span = (wheels[1].translation - wheels[0].translation).normalize();
|
|
||||||
|
|
||||||
for (entity, xform, mut cgroups) in wheel_query.iter_mut() {
|
|
||||||
//let ray_dir = xform.translation.normalize();
|
|
||||||
let ray_dir = xform.right().cross(span).normalize();
|
|
||||||
if let Some(hit) = context.cast_ray_and_get_normal(
|
|
||||||
xform.translation,
|
|
||||||
ray_dir,
|
|
||||||
config.radius * 1.1,
|
|
||||||
false,
|
|
||||||
QueryFilter::only_fixed(),
|
|
||||||
) {
|
|
||||||
cgroups.memberships = Group::NONE;
|
|
||||||
cgroups.filters = Group::NONE;
|
|
||||||
commands.entity(entity).insert(Tunneling {
|
|
||||||
frames: 3,
|
|
||||||
dir: hit.1.normal,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn tunnel_out(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut wheel_query: Query<
|
|
||||||
(
|
|
||||||
Entity,
|
|
||||||
&mut Tunneling,
|
|
||||||
&mut ExternalForce,
|
|
||||||
&mut CollisionGroups,
|
|
||||||
),
|
|
||||||
With<CyberWheel>,
|
|
||||||
>,
|
|
||||||
mprops: Query<&ReadMassProperties, With<CyberBikeBody>>,
|
|
||||||
settings: Res<MovementSettings>,
|
|
||||||
) {
|
|
||||||
let mprops = mprops.single();
|
|
||||||
for (entity, mut tunneling, mut force, mut cgroups) in wheel_query.iter_mut() {
|
|
||||||
if tunneling.frames == 0 {
|
|
||||||
commands.entity(entity).remove::<Tunneling>();
|
|
||||||
force.force = Vec3::ZERO;
|
|
||||||
(cgroups.memberships, cgroups.filters) = BIKE_WHEEL_COLLISION_GROUP;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
tunneling.frames -= 1;
|
|
||||||
force.force = tunneling.dir * settings.gravity * 1.5 * mprops.0.mass;
|
|
||||||
#[cfg(feature = "inspector")]
|
|
||||||
dbg!(&tunneling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// General velocity-based drag-force calculation; does not take orientation
|
|
||||||
/// into account.
|
|
||||||
pub(super) fn drag(mut query: Query<(&Velocity, &mut ExternalForce), With<CyberBikeBody>>) {
|
|
||||||
let (vels, mut forces) = query.single_mut();
|
|
||||||
|
|
||||||
if let Some(vel) = vels.linvel.try_normalize() {
|
|
||||||
let v2 = vels.linvel.length_squared();
|
|
||||||
forces.force -= vel * (v2 * 0.02);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
|
use avian3d::prelude::*;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::{AssetServer, BuildChildren, Commands, Quat, Res, SpatialBundle, Transform, Vec3},
|
prelude::{AssetServer, BuildChildren, Commands, Quat, Res, SpatialBundle, Transform, Vec3},
|
||||||
scene::SceneBundle,
|
scene::SceneBundle,
|
||||||
};
|
};
|
||||||
use bevy_rapier3d::prelude::{
|
|
||||||
Ccd, Collider, ColliderMassProperties, CollisionGroups, Damping, ExternalForce, Friction,
|
|
||||||
ReadMassProperties, Restitution, RigidBody, Sleeping, TransformInterpolation, Velocity,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{spawn_wheels, CyberBikeBody, Meshterial, WheelConfig, BIKE_BODY_COLLISION_GROUP};
|
use super::{spawn_wheels, CyberBikeBody, Meshterial, WheelConfig, BIKE_BODY_COLLISION_GROUP};
|
||||||
use crate::{action::CatControllerState, planet::PLANET_RADIUS};
|
use crate::{action::CatControllerState, planet::PLANET_RADIUS};
|
||||||
|
@ -24,14 +21,10 @@ pub(super) fn spawn_cyberbike(
|
||||||
let right = xform.right() * 350.0;
|
let right = xform.right() * 350.0;
|
||||||
xform.translation += right;
|
xform.translation += right;
|
||||||
|
|
||||||
let damping = Damping {
|
|
||||||
angular_damping: 2.0,
|
|
||||||
linear_damping: 0.1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let friction = Friction {
|
let friction = Friction {
|
||||||
coefficient: 0.01,
|
dynamic_coefficient: 0.1,
|
||||||
..Default::default()
|
static_coefficient: 0.6,
|
||||||
|
combine_rule: CoefficientCombine::Average,
|
||||||
};
|
};
|
||||||
|
|
||||||
let restitution = Restitution {
|
let restitution = Restitution {
|
||||||
|
@ -39,46 +32,38 @@ pub(super) fn spawn_cyberbike(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mass_properties = ColliderMassProperties::Density(1.5);
|
|
||||||
|
|
||||||
let (membership, filter) = BIKE_BODY_COLLISION_GROUP;
|
let (membership, filter) = BIKE_BODY_COLLISION_GROUP;
|
||||||
let bike_collision_group = CollisionGroups::new(membership, filter);
|
let bike_collision_group = CollisionLayers::new(membership, filter);
|
||||||
|
|
||||||
let scene = asset_server.load("cb-no-y_up.glb#Scene0");
|
let scene = asset_server.load("cb-no-y_up.glb#Scene0");
|
||||||
|
|
||||||
let spatialbundle = SpatialBundle {
|
let body_collider =
|
||||||
transform: xform,
|
Collider::capsule_endpoints(0.5, Vec3::new(0.0, 0.0, -0.65), Vec3::new(0.0, 0.0, 0.8));
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let bike = commands
|
let bike = commands
|
||||||
.spawn(RigidBody::Dynamic)
|
.spawn(SpatialBundle::from_transform(xform))
|
||||||
.insert(spatialbundle)
|
|
||||||
.insert((
|
.insert((
|
||||||
Collider::capsule(Vec3::new(0.0, 0.0, -0.65), Vec3::new(0.0, 0.0, 0.8), 0.5),
|
RigidBody::Dynamic,
|
||||||
|
body_collider,
|
||||||
bike_collision_group,
|
bike_collision_group,
|
||||||
mass_properties,
|
|
||||||
damping,
|
|
||||||
restitution,
|
restitution,
|
||||||
friction,
|
friction,
|
||||||
Sleeping::disabled(),
|
SleepingDisabled,
|
||||||
Ccd { enabled: true },
|
CyberBikeBody,
|
||||||
ReadMassProperties::default(),
|
CatControllerState::default(),
|
||||||
|
ColliderDensity(0.6),
|
||||||
|
LinearDamping(0.1),
|
||||||
|
AngularDamping(2.0),
|
||||||
|
LinearVelocity::ZERO,
|
||||||
|
AngularVelocity::ZERO,
|
||||||
|
ExternalForce::ZERO,
|
||||||
|
ExternalTorque::ZERO,
|
||||||
))
|
))
|
||||||
.insert(TransformInterpolation {
|
|
||||||
start: None,
|
|
||||||
end: None,
|
|
||||||
})
|
|
||||||
.insert(Velocity::zero())
|
|
||||||
.insert(ExternalForce::default())
|
|
||||||
.with_children(|rider| {
|
.with_children(|rider| {
|
||||||
rider.spawn(SceneBundle {
|
rider.spawn(SceneBundle {
|
||||||
scene,
|
scene,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.insert(CyberBikeBody)
|
|
||||||
.insert(CatControllerState::default())
|
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
spawn_wheels(&mut commands, bike, &wheel_conf, &mut meshterials);
|
spawn_wheels(&mut commands, bike, &wheel_conf, &mut meshterials);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
math::Vec3,
|
||||||
prelude::{Component, ReflectResource, Resource},
|
prelude::{Component, ReflectResource, Resource},
|
||||||
reflect::Reflect,
|
reflect::Reflect,
|
||||||
};
|
};
|
||||||
|
@ -6,12 +7,18 @@ use bevy::{
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct CyberBikeBody;
|
pub struct CyberBikeBody;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Clone, Copy, Component)]
|
||||||
pub struct CyberSteering;
|
pub struct CyberSteering;
|
||||||
|
|
||||||
#[derive(Debug, Component)]
|
#[derive(Clone, Copy, Debug, Component)]
|
||||||
pub struct CyberWheel;
|
pub struct CyberWheel;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Component)]
|
||||||
|
pub struct CyberSpring(pub Vec3);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Component)]
|
||||||
|
pub struct CyberFork;
|
||||||
|
|
||||||
#[derive(Resource, Reflect)]
|
#[derive(Resource, Reflect)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
pub struct WheelConfig {
|
pub struct WheelConfig {
|
||||||
|
@ -35,7 +42,7 @@ impl Default for WheelConfig {
|
||||||
rear_back: 1.0,
|
rear_back: 1.0,
|
||||||
y: -0.1,
|
y: -0.1,
|
||||||
limits: [-0.5, 0.1],
|
limits: [-0.5, 0.1],
|
||||||
stiffness: 190.0,
|
stiffness: 3.0,
|
||||||
damping: 8.0,
|
damping: 8.0,
|
||||||
radius: 0.25,
|
radius: 0.25,
|
||||||
thickness: 0.11,
|
thickness: 0.11,
|
||||||
|
|
|
@ -2,16 +2,16 @@ mod body;
|
||||||
mod components;
|
mod components;
|
||||||
mod wheels;
|
mod wheels;
|
||||||
|
|
||||||
use bevy::prelude::{
|
use bevy::{
|
||||||
App, Assets, IntoSystemConfig, Mesh, Plugin, ResMut, StandardMaterial, StartupSet,
|
app::{PostStartup, Startup},
|
||||||
|
prelude::{App, Assets, Mesh, Plugin, ResMut, StandardMaterial},
|
||||||
};
|
};
|
||||||
use bevy_rapier3d::prelude::Group;
|
|
||||||
|
|
||||||
pub(crate) use self::components::*;
|
pub(crate) use self::components::*;
|
||||||
use self::{body::spawn_cyberbike, wheels::spawn_wheels};
|
use self::{body::spawn_cyberbike, wheels::spawn_wheels};
|
||||||
|
|
||||||
pub const BIKE_BODY_COLLISION_GROUP: (Group, Group) = (Group::GROUP_1, Group::GROUP_1);
|
pub const BIKE_BODY_COLLISION_GROUP: (u32, u32) = (0b01, 0b01);
|
||||||
pub const BIKE_WHEEL_COLLISION_GROUP: (Group, Group) = (Group::GROUP_10, Group::GROUP_10);
|
pub const BIKE_WHEEL_COLLISION_GROUP: (u32, u32) = (0b10, 0b10);
|
||||||
|
|
||||||
type Meshterial<'a> = (
|
type Meshterial<'a> = (
|
||||||
ResMut<'a, Assets<Mesh>>,
|
ResMut<'a, Assets<Mesh>>,
|
||||||
|
@ -23,6 +23,6 @@ impl Plugin for CyberBikePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.insert_resource(WheelConfig::default())
|
app.insert_resource(WheelConfig::default())
|
||||||
.register_type::<WheelConfig>()
|
.register_type::<WheelConfig>()
|
||||||
.add_startup_system(spawn_cyberbike.in_base_set(StartupSet::PostStartup));
|
.add_systems(Startup, spawn_cyberbike);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use bevy::prelude::{shape::Torus as Tire, *};
|
use avian3d::prelude::*;
|
||||||
use bevy_rapier3d::prelude::{
|
use bevy::prelude::{Torus as Tire, *};
|
||||||
Ccd, CoefficientCombineRule, Collider, ColliderMassProperties, CollisionGroups, Damping,
|
|
||||||
ExternalForce, Friction, MultibodyJoint, PrismaticJointBuilder, Restitution,
|
|
||||||
RevoluteJointBuilder, RigidBody, Sleeping, TransformInterpolation,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{CyberSteering, CyberWheel, Meshterial, WheelConfig, BIKE_WHEEL_COLLISION_GROUP};
|
use super::{
|
||||||
|
CyberFork, CyberSpring, CyberSteering, CyberWheel, Meshterial, WheelConfig,
|
||||||
|
BIKE_WHEEL_COLLISION_GROUP,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn spawn_wheels(
|
pub fn spawn_wheels(
|
||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
|
@ -14,18 +13,18 @@ pub fn spawn_wheels(
|
||||||
meshterials: &mut Meshterial,
|
meshterials: &mut Meshterial,
|
||||||
) {
|
) {
|
||||||
let (membership, filter) = BIKE_WHEEL_COLLISION_GROUP;
|
let (membership, filter) = BIKE_WHEEL_COLLISION_GROUP;
|
||||||
let wheels_collision_group = CollisionGroups::new(membership, filter);
|
let wheels_collision_group = CollisionLayers::new(membership, filter);
|
||||||
let wheel_y = conf.y;
|
let wheel_y = conf.y;
|
||||||
let stiffness = conf.stiffness;
|
let not_sleeping = SleepingDisabled;
|
||||||
let not_sleeping = Sleeping::disabled();
|
let ccd = SweptCcd::NON_LINEAR.include_dynamic(false);
|
||||||
let ccd = Ccd { enabled: true };
|
|
||||||
let limits = conf.limits;
|
let limits = conf.limits;
|
||||||
let (meshes, materials) = meshterials;
|
let (meshes, materials) = meshterials;
|
||||||
let rake_vec: Vec3 = Vec3::new(0.0, 1.0, 0.57).normalize(); // about 30 degrees of rake
|
let rake_vec: Vec3 = Vec3::new(0.0, 1.0, 0.57).normalize(); // about 30 degrees of rake
|
||||||
|
|
||||||
let friction = Friction {
|
let friction = Friction {
|
||||||
coefficient: conf.friction,
|
dynamic_coefficient: conf.friction,
|
||||||
combine_rule: CoefficientCombineRule::Average,
|
static_coefficient: conf.friction,
|
||||||
|
combine_rule: CoefficientCombine::Average,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut wheel_poses = Vec::with_capacity(2);
|
let mut wheel_poses = Vec::with_capacity(2);
|
||||||
|
@ -50,12 +49,7 @@ pub fn spawn_wheels(
|
||||||
let (mesh, collider) = gen_tires(conf);
|
let (mesh, collider) = gen_tires(conf);
|
||||||
|
|
||||||
let material = StandardMaterial {
|
let material = StandardMaterial {
|
||||||
base_color: Color::Rgba {
|
base_color: Color::linear_rgba(0.01, 0.01, 0.01, 1.0),
|
||||||
red: 0.01,
|
|
||||||
green: 0.01,
|
|
||||||
blue: 0.01,
|
|
||||||
alpha: 1.0,
|
|
||||||
},
|
|
||||||
alpha_mode: AlphaMode::Opaque,
|
alpha_mode: AlphaMode::Opaque,
|
||||||
perceptual_roughness: 0.5,
|
perceptual_roughness: 0.5,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -67,64 +61,70 @@ pub fn spawn_wheels(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mass_props = ColliderMassProperties::Density(conf.density);
|
|
||||||
let suspension_damping = conf.damping;
|
|
||||||
|
|
||||||
let suspension_axis = if steering.is_some() {
|
let suspension_axis = if steering.is_some() {
|
||||||
rake_vec
|
rake_vec
|
||||||
} else {
|
} else {
|
||||||
Vec3::Y
|
Vec3::Y
|
||||||
};
|
};
|
||||||
|
|
||||||
let suspension_joint_builder = PrismaticJointBuilder::new(suspension_axis)
|
let wheel = commands
|
||||||
.local_anchor1(offset)
|
.spawn(pbr_bundle)
|
||||||
.limits(limits)
|
.insert((
|
||||||
.motor_position(limits[0], stiffness, suspension_damping);
|
collider,
|
||||||
let suspension_joint = MultibodyJoint::new(bike, suspension_joint_builder);
|
ccd,
|
||||||
let fork_rb_entity = commands
|
not_sleeping,
|
||||||
.spawn(RigidBody::Dynamic)
|
wheels_collision_group,
|
||||||
.insert(suspension_joint)
|
friction,
|
||||||
.insert(not_sleeping)
|
CyberWheel,
|
||||||
|
ExternalForce::default(),
|
||||||
|
Restitution::new(conf.restitution),
|
||||||
|
SpatialBundle::default(),
|
||||||
|
RigidBody::Dynamic,
|
||||||
|
ColliderDensity(0.1),
|
||||||
|
AngularVelocity::ZERO,
|
||||||
|
ExternalTorque::ZERO,
|
||||||
|
))
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
let axel_parent_entity = if let Some(steering) = steering {
|
let spring = CyberSpring(suspension_axis);
|
||||||
let neck_builder =
|
let axel = if let Some(steering) = steering {
|
||||||
RevoluteJointBuilder::new(rake_vec).local_anchor1(Vec3::new(0.0, 0.0, 0.1)); // this adds another 0.1m of trail
|
commands.spawn((
|
||||||
let neck_joint = MultibodyJoint::new(fork_rb_entity, neck_builder);
|
RigidBody::Dynamic,
|
||||||
let neck = commands
|
ExternalForce::ZERO,
|
||||||
.spawn(RigidBody::Dynamic)
|
steering,
|
||||||
.insert(neck_joint)
|
spring,
|
||||||
.insert(steering)
|
Collider::sphere(0.1),
|
||||||
.insert(not_sleeping)
|
ColliderDensity(0.1),
|
||||||
.id();
|
))
|
||||||
neck
|
|
||||||
} else {
|
} else {
|
||||||
fork_rb_entity
|
commands.spawn((
|
||||||
};
|
RigidBody::Dynamic,
|
||||||
|
ExternalForce::ZERO,
|
||||||
|
spring,
|
||||||
|
Collider::sphere(0.1),
|
||||||
|
ColliderDensity(0.1),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
.insert(SpatialBundle::default())
|
||||||
|
.id();
|
||||||
|
let axel_joint = RevoluteJoint::new(axel, wheel).with_aligned_axis(Vec3::NEG_X);
|
||||||
|
commands.spawn(axel_joint);
|
||||||
|
|
||||||
let axel_builder = RevoluteJointBuilder::new(Vec3::X);
|
// suspension and steering:
|
||||||
let axel_joint = MultibodyJoint::new(axel_parent_entity, axel_builder);
|
if steering.is_some() {
|
||||||
let wheel_damping = Damping {
|
let joint = PrismaticJoint::new(axel, bike)
|
||||||
linear_damping: 0.8,
|
.with_free_axis(suspension_axis)
|
||||||
..Default::default()
|
.with_local_anchor_1(Vec3::new(0.0, 0.0, 0.1))
|
||||||
|
.with_local_anchor_2(offset)
|
||||||
|
.with_limits(limits[0], limits[1]);
|
||||||
|
commands.spawn((joint, CyberFork));
|
||||||
|
} else {
|
||||||
|
let joint = PrismaticJoint::new(axel, bike)
|
||||||
|
.with_free_axis(suspension_axis)
|
||||||
|
.with_local_anchor_2(offset)
|
||||||
|
.with_limits(limits[0], limits[1]);
|
||||||
|
commands.spawn(joint);
|
||||||
};
|
};
|
||||||
|
|
||||||
commands.spawn(pbr_bundle).insert((
|
|
||||||
collider,
|
|
||||||
mass_props,
|
|
||||||
wheel_damping,
|
|
||||||
ccd,
|
|
||||||
not_sleeping,
|
|
||||||
axel_joint,
|
|
||||||
wheels_collision_group,
|
|
||||||
friction,
|
|
||||||
CyberWheel,
|
|
||||||
ExternalForce::default(),
|
|
||||||
Restitution::new(conf.restitution),
|
|
||||||
SpatialBundle::default(),
|
|
||||||
TransformInterpolation::default(),
|
|
||||||
RigidBody::Dynamic,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,9 +133,8 @@ fn gen_tires(conf: &WheelConfig) -> (Mesh, Collider) {
|
||||||
let wheel_rad = conf.radius;
|
let wheel_rad = conf.radius;
|
||||||
let tire_thickness = conf.thickness;
|
let tire_thickness = conf.thickness;
|
||||||
let tire = Tire {
|
let tire = Tire {
|
||||||
radius: wheel_rad,
|
minor_radius: tire_thickness,
|
||||||
ring_radius: tire_thickness,
|
major_radius: wheel_rad,
|
||||||
..Default::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut mesh = Mesh::from(tire);
|
let mut mesh = Mesh::from(tire);
|
||||||
|
@ -161,17 +160,7 @@ fn gen_tires(conf: &WheelConfig) -> (Mesh, Collider) {
|
||||||
for idx in indices.as_slice().chunks_exact(3) {
|
for idx in indices.as_slice().chunks_exact(3) {
|
||||||
idxs.push([idx[0] as u32, idx[1] as u32, idx[2] as u32]);
|
idxs.push([idx[0] as u32, idx[1] as u32, idx[2] as u32]);
|
||||||
}
|
}
|
||||||
let wheel_collider = Collider::convex_decomposition(
|
let wheel_collider = Collider::convex_decomposition_from_mesh(&mesh).unwrap();
|
||||||
&mesh
|
|
||||||
.attribute(Mesh::ATTRIBUTE_POSITION)
|
|
||||||
.unwrap()
|
|
||||||
.as_float3()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|v| Vec3::from_array(*v))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
&idxs,
|
|
||||||
);
|
|
||||||
|
|
||||||
(mesh, wheel_collider)
|
(mesh, wheel_collider)
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ fn follow_cyberbike(
|
||||||
// handle input pitch
|
// handle input pitch
|
||||||
let angle = input.pitch.powi(3) * MAX_PITCH;
|
let angle = input.pitch.powi(3) * MAX_PITCH;
|
||||||
let axis = cam_xform.right();
|
let axis = cam_xform.right();
|
||||||
cam_xform.rotate(Quat::from_axis_angle(axis, angle));
|
cam_xform.rotate(Quat::from_axis_angle(*axis, angle));
|
||||||
}
|
}
|
||||||
CyberCameras::Debug => {
|
CyberCameras::Debug => {
|
||||||
let mut ncx = bike_xform.to_owned();
|
let mut ncx = bike_xform.to_owned();
|
||||||
|
@ -101,7 +101,7 @@ fn update_active_camera(
|
||||||
) {
|
) {
|
||||||
// find the camera with the current state, set it as the ActiveCamera
|
// find the camera with the current state, set it as the ActiveCamera
|
||||||
query.iter_mut().for_each(|(mut cam, cyber)| {
|
query.iter_mut().for_each(|(mut cam, cyber)| {
|
||||||
if cyber.eq(&state.0) {
|
if cyber.eq(&state.get()) {
|
||||||
cam.is_active = true;
|
cam.is_active = true;
|
||||||
} else {
|
} else {
|
||||||
cam.is_active = false;
|
cam.is_active = false;
|
||||||
|
@ -112,13 +112,13 @@ fn update_active_camera(
|
||||||
fn cycle_cam_state(
|
fn cycle_cam_state(
|
||||||
state: Res<State<CyberCameras>>,
|
state: Res<State<CyberCameras>>,
|
||||||
mut next: ResMut<NextState<CyberCameras>>,
|
mut next: ResMut<NextState<CyberCameras>>,
|
||||||
mut keys: ResMut<Input<KeyCode>>,
|
mut keys: ResMut<ButtonInput<KeyCode>>,
|
||||||
) {
|
) {
|
||||||
if keys.just_pressed(KeyCode::D) {
|
if keys.just_pressed(KeyCode::KeyD) {
|
||||||
let new_state = state.0.next();
|
let new_state = state.get().next();
|
||||||
info!("{:?}", new_state);
|
info!("{:?}", new_state);
|
||||||
next.set(new_state);
|
next.set(new_state);
|
||||||
keys.reset(KeyCode::D);
|
keys.reset(KeyCode::KeyD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,9 +132,10 @@ impl Plugin for CyberCamPlugin {
|
||||||
|
|
||||||
fn common(app: &mut bevy::prelude::App) {
|
fn common(app: &mut bevy::prelude::App) {
|
||||||
app.insert_resource(DebugCamOffset::default())
|
app.insert_resource(DebugCamOffset::default())
|
||||||
.add_startup_system(setup_cybercams)
|
.add_systems(Startup, setup_cybercams)
|
||||||
.add_state::<CyberCameras>()
|
.init_state::<CyberCameras>()
|
||||||
.add_system(cycle_cam_state)
|
.add_systems(
|
||||||
.add_system(update_active_camera)
|
Update,
|
||||||
.add_system(follow_cyberbike);
|
(cycle_cam_state, update_active_camera, follow_cyberbike),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
use bevy::prelude::{App, Color, Plugin};
|
use avian3d::debug_render::PhysicsGizmos;
|
||||||
|
use bevy::{
|
||||||
|
color::Srgba,
|
||||||
|
gizmos::AppGizmoBuilder,
|
||||||
|
prelude::{App, Color, GizmoConfig, Plugin},
|
||||||
|
};
|
||||||
|
|
||||||
// use crate::planet::CyberPlanet;
|
// use crate::planet::CyberPlanet;
|
||||||
|
|
||||||
|
@ -9,26 +14,16 @@ pub struct CyberGlamorPlugin;
|
||||||
impl Plugin for CyberGlamorPlugin {
|
impl Plugin for CyberGlamorPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
{
|
{
|
||||||
use bevy_rapier3d::render::{
|
let plugin = avian3d::debug_render::PhysicsDebugPlugin::default();
|
||||||
DebugRenderMode, DebugRenderStyle, RapierDebugRenderPlugin,
|
|
||||||
};
|
|
||||||
let style = DebugRenderStyle {
|
|
||||||
multibody_joint_anchor_color: Color::GREEN.as_rgba_f32(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let mode = DebugRenderMode::CONTACTS
|
|
||||||
| DebugRenderMode::SOLVER_CONTACTS
|
|
||||||
| DebugRenderMode::JOINTS
|
|
||||||
| DebugRenderMode::RIGID_BODY_AXES;
|
|
||||||
|
|
||||||
let rplugin = RapierDebugRenderPlugin {
|
app.add_plugins(plugin).insert_gizmo_config(
|
||||||
style,
|
PhysicsGizmos {
|
||||||
always_on_top: true,
|
contact_point_color: Some(Srgba::GREEN.into()),
|
||||||
enabled: true,
|
contact_normal_color: Some(Srgba::WHITE.into()),
|
||||||
mode,
|
..Default::default()
|
||||||
};
|
},
|
||||||
|
GizmoConfig::default(),
|
||||||
app.add_plugin(rplugin);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
22
src/input.rs
22
src/input.rs
|
@ -14,22 +14,22 @@ pub(crate) struct InputState {
|
||||||
pub pitch: f32,
|
pub pitch: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<Input<KeyCode>>) {
|
fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<ButtonInput<KeyCode>>) {
|
||||||
let keyset: HashSet<_> = keys.get_pressed().collect();
|
let keyset: HashSet<_> = keys.get_pressed().collect();
|
||||||
let shifted = keyset.contains(&KeyCode::LShift) || keyset.contains(&KeyCode::RShift);
|
let shifted = keyset.contains(&KeyCode::ShiftLeft) || keyset.contains(&KeyCode::ShiftRight);
|
||||||
|
|
||||||
for key in keyset {
|
for key in keyset {
|
||||||
match key {
|
match key {
|
||||||
KeyCode::Left => offset.rot -= 5.0,
|
KeyCode::ArrowLeft => offset.rot -= 5.0,
|
||||||
KeyCode::Right => offset.rot += 5.0,
|
KeyCode::ArrowRight => offset.rot += 5.0,
|
||||||
KeyCode::Up => {
|
KeyCode::ArrowUp => {
|
||||||
if shifted {
|
if shifted {
|
||||||
offset.alt += 0.5;
|
offset.alt += 0.5;
|
||||||
} else {
|
} else {
|
||||||
offset.dist -= 0.5;
|
offset.dist -= 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::ArrowDown => {
|
||||||
if shifted {
|
if shifted {
|
||||||
offset.alt -= 0.5;
|
offset.alt -= 0.5;
|
||||||
} else {
|
} else {
|
||||||
|
@ -41,16 +41,17 @@ fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<Input<K
|
||||||
}
|
}
|
||||||
|
|
||||||
if keys.get_just_released().len() > 0 {
|
if keys.get_just_released().len() > 0 {
|
||||||
let unpressed = keys.just_released(KeyCode::LShift) || keys.just_released(KeyCode::RShift);
|
let unpressed =
|
||||||
|
keys.just_released(KeyCode::ShiftLeft) || keys.just_released(KeyCode::ShiftRight);
|
||||||
keys.reset_all();
|
keys.reset_all();
|
||||||
if shifted && !unpressed {
|
if shifted && !unpressed {
|
||||||
keys.press(KeyCode::LShift);
|
keys.press(KeyCode::ShiftLeft);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_input(mut events: EventReader<GamepadEvent>, mut istate: ResMut<InputState>) {
|
fn update_input(mut events: EventReader<GamepadEvent>, mut istate: ResMut<InputState>) {
|
||||||
for pad_event in events.iter() {
|
for pad_event in events.read() {
|
||||||
match pad_event {
|
match pad_event {
|
||||||
GamepadEvent::Button(button_event) => {
|
GamepadEvent::Button(button_event) => {
|
||||||
let GamepadButtonChangedEvent {
|
let GamepadButtonChangedEvent {
|
||||||
|
@ -92,7 +93,6 @@ pub struct CyberInputPlugin;
|
||||||
impl Plugin for CyberInputPlugin {
|
impl Plugin for CyberInputPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_resource::<InputState>()
|
app.init_resource::<InputState>()
|
||||||
.add_system(update_input)
|
.add_systems(Update, (update_input, update_debug_cam));
|
||||||
.add_system(update_debug_cam);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@ use crate::planet::PLANET_RADIUS;
|
||||||
|
|
||||||
pub const LIGHT_RANGE: f32 = 90.0;
|
pub const LIGHT_RANGE: f32 = 90.0;
|
||||||
|
|
||||||
|
static BLUE: Color = Color::linear_rgb(0.0, 0.0, 1.0);
|
||||||
|
static PINK: Color = Color::linear_rgb(199.0 / 255.0, 21.0 / 255.0, 113.0 / 255.0);
|
||||||
|
|
||||||
fn spawn_static_lights(
|
fn spawn_static_lights(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
@ -12,7 +15,7 @@ fn spawn_static_lights(
|
||||||
let pink_light = PointLight {
|
let pink_light = PointLight {
|
||||||
intensity: 1_00.0,
|
intensity: 1_00.0,
|
||||||
range: LIGHT_RANGE,
|
range: LIGHT_RANGE,
|
||||||
color: Color::PINK,
|
color: PINK,
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -21,7 +24,7 @@ fn spawn_static_lights(
|
||||||
let blue_light = PointLight {
|
let blue_light = PointLight {
|
||||||
intensity: 1_000.0,
|
intensity: 1_000.0,
|
||||||
range: LIGHT_RANGE,
|
range: LIGHT_RANGE,
|
||||||
color: Color::BLUE,
|
color: BLUE,
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -48,16 +51,10 @@ fn spawn_static_lights(
|
||||||
})
|
})
|
||||||
.with_children(|builder| {
|
.with_children(|builder| {
|
||||||
builder.spawn(PbrBundle {
|
builder.spawn(PbrBundle {
|
||||||
mesh: meshes.add(
|
mesh: meshes.add(Mesh::from(Sphere::new(10.0))),
|
||||||
Mesh::try_from(shape::Icosphere {
|
|
||||||
radius: 10.0,
|
|
||||||
subdivisions: 2,
|
|
||||||
})
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
material: materials.add(StandardMaterial {
|
material: materials.add(StandardMaterial {
|
||||||
base_color: Color::BLUE,
|
base_color: BLUE,
|
||||||
emissive: Color::PINK,
|
emissive: PINK.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -72,16 +69,10 @@ fn spawn_static_lights(
|
||||||
})
|
})
|
||||||
.with_children(|builder| {
|
.with_children(|builder| {
|
||||||
builder.spawn(PbrBundle {
|
builder.spawn(PbrBundle {
|
||||||
mesh: meshes.add(
|
mesh: meshes.add(Mesh::from(Sphere::new(10.0))),
|
||||||
Mesh::try_from(shape::Icosphere {
|
|
||||||
radius: 10.0,
|
|
||||||
subdivisions: 2,
|
|
||||||
})
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
material: materials.add(StandardMaterial {
|
material: materials.add(StandardMaterial {
|
||||||
base_color: Color::PINK,
|
base_color: PINK,
|
||||||
emissive: Color::BLUE,
|
emissive: BLUE.into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -92,6 +83,6 @@ fn spawn_static_lights(
|
||||||
pub struct CyberSpaceLightsPlugin;
|
pub struct CyberSpaceLightsPlugin;
|
||||||
impl Plugin for CyberSpaceLightsPlugin {
|
impl Plugin for CyberSpaceLightsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_startup_system(spawn_static_lights);
|
app.add_systems(Startup, spawn_static_lights);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
src/main.rs
38
src/main.rs
|
@ -8,7 +8,7 @@ use cyber_rider::{
|
||||||
};
|
};
|
||||||
|
|
||||||
//const CYBER_SKY: Color = Color::rgb(0.07, 0.001, 0.02);
|
//const CYBER_SKY: Color = Color::rgb(0.07, 0.001, 0.02);
|
||||||
const CYBER_SKY: Color = Color::rgb(0.64, 0.745, 0.937); // a light blue sky
|
const CYBER_SKY: Color = Color::srgb(0.64, 0.745, 0.937); // a light blue sky
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
@ -21,18 +21,36 @@ fn main() {
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(CyberPlanetPlugin)
|
.add_plugins((
|
||||||
.add_plugin(CyberInputPlugin)
|
CyberPlanetPlugin,
|
||||||
.add_plugin(CyberActionPlugin)
|
CyberInputPlugin,
|
||||||
.add_plugin(CyberCamPlugin)
|
CyberActionPlugin,
|
||||||
.add_plugin(CyberSpaceLightsPlugin)
|
CyberCamPlugin,
|
||||||
.add_plugin(CyberUIPlugin)
|
CyberSpaceLightsPlugin,
|
||||||
.add_plugin(CyberBikePlugin)
|
CyberUIPlugin,
|
||||||
.add_startup_system(disable_mouse_trap)
|
CyberBikePlugin,
|
||||||
.add_system(bevy::window::close_on_esc);
|
))
|
||||||
|
.add_systems(Startup, disable_mouse_trap)
|
||||||
|
.add_systems(Update, close_on_esc);
|
||||||
|
|
||||||
#[cfg(feature = "inspector")]
|
#[cfg(feature = "inspector")]
|
||||||
app.add_plugin(CyberGlamorPlugin);
|
app.add_plugin(CyberGlamorPlugin);
|
||||||
|
|
||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_on_esc(
|
||||||
|
mut commands: Commands,
|
||||||
|
focused_windows: Query<(Entity, &Window)>,
|
||||||
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
|
) {
|
||||||
|
for (window, focus) in focused_windows.iter() {
|
||||||
|
if !focus.focused {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.just_pressed(KeyCode::Escape) {
|
||||||
|
commands.entity(window).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
use avian3d::prelude::*;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::{shape::Icosphere, *},
|
prelude::*,
|
||||||
render::{color::Color, mesh::Indices},
|
render::{mesh::Indices, render_asset::RenderAssetUsages},
|
||||||
};
|
};
|
||||||
use bevy_rapier3d::prelude::*;
|
|
||||||
use hexasphere::shapes::IcoSphere;
|
use hexasphere::shapes::IcoSphere;
|
||||||
use noise::{HybridMulti, NoiseFn, SuperSimplex};
|
use noise::{HybridMulti, NoiseFn, SuperSimplex};
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
|
@ -20,20 +20,12 @@ fn spawn_planet(
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
//let color = Color::rgb(0.74, 0.5334, 0.176);
|
let (mesh, shape) = gen_planet(PLANET_RADIUS);
|
||||||
let isphere = Icosphere {
|
|
||||||
radius: PLANET_RADIUS,
|
|
||||||
subdivisions: 88,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (mesh, shape) = gen_planet(isphere);
|
|
||||||
|
|
||||||
let pbody = (RigidBody::Fixed, Ccd { enabled: true });
|
|
||||||
|
|
||||||
let pcollide = (
|
let pcollide = (
|
||||||
shape,
|
shape,
|
||||||
Friction {
|
Friction {
|
||||||
coefficient: 1.2,
|
static_coefficient: 1.2,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Restitution::new(0.8),
|
Restitution::new(0.8),
|
||||||
|
@ -42,18 +34,22 @@ fn spawn_planet(
|
||||||
commands
|
commands
|
||||||
.spawn(PbrBundle {
|
.spawn(PbrBundle {
|
||||||
mesh: meshes.add(mesh),
|
mesh: meshes.add(mesh),
|
||||||
material: materials.add(Color::WHITE.into()),
|
material: materials.add(Color::WHITE),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.insert(pbody)
|
.insert((
|
||||||
.insert(pcollide)
|
RigidBody::Static,
|
||||||
.insert(CyberPlanet);
|
pcollide,
|
||||||
|
CyberPlanet,
|
||||||
|
CollisionLayers::new(u32::MAX, u32::MAX),
|
||||||
|
CollisionMargin(0.2),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CyberPlanetPlugin;
|
pub struct CyberPlanetPlugin;
|
||||||
impl Plugin for CyberPlanetPlugin {
|
impl Plugin for CyberPlanetPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_startup_system(spawn_planet);
|
app.add_systems(Startup, spawn_planet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +57,10 @@ impl Plugin for CyberPlanetPlugin {
|
||||||
// utils
|
// utils
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
fn gen_planet(sphere: Icosphere) -> (Mesh, Collider) {
|
fn gen_planet(radius: f32) -> (Mesh, Collider) {
|
||||||
// straight-up stolen from Bevy's impl of Mesh from Icosphere, so I can do the
|
// straight-up stolen from Bevy's impl of Mesh from Icosphere, so I can do the
|
||||||
// displacement before normals are calculated.
|
// displacement before normals are calculated.
|
||||||
let generated = IcoSphere::new(sphere.subdivisions, |point| {
|
let generated = IcoSphere::new(79, |point| {
|
||||||
let inclination = point.y.acos();
|
let inclination = point.y.acos();
|
||||||
let azimuth = point.z.atan2(point.x);
|
let azimuth = point.z.atan2(point.x);
|
||||||
|
|
||||||
|
@ -90,7 +86,7 @@ fn gen_planet(sphere: Icosphere) -> (Mesh, Collider) {
|
||||||
|
|
||||||
let points = noisy_points
|
let points = noisy_points
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&p| (Vec3::from_slice(&p) * sphere.radius).into())
|
.map(|&p| (Vec3::from_slice(&p) * radius).into())
|
||||||
.collect::<Vec<[f32; 3]>>();
|
.collect::<Vec<[f32; 3]>>();
|
||||||
|
|
||||||
for p in &points {
|
for p in &points {
|
||||||
|
@ -108,10 +104,13 @@ fn gen_planet(sphere: Icosphere) -> (Mesh, Collider) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let indices = Indices::U32(indices);
|
let indices = Indices::U32(indices);
|
||||||
let collider = Collider::trimesh(points.iter().map(|p| Vect::from_slice(p)).collect(), idxs);
|
let collider = Collider::trimesh(points.iter().map(|p| Vec3::from_slice(p)).collect(), idxs);
|
||||||
|
|
||||||
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
|
let mut mesh = Mesh::new(
|
||||||
mesh.set_indices(Some(indices));
|
PrimitiveTopology::TriangleList,
|
||||||
|
RenderAssetUsages::default(),
|
||||||
|
);
|
||||||
|
mesh.insert_indices(indices);
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, points);
|
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, points);
|
||||||
//mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
//mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
||||||
mesh.duplicate_vertices();
|
mesh.duplicate_vertices();
|
||||||
|
@ -129,7 +128,9 @@ fn gen_planet(sphere: Icosphere) -> (Mesh, Collider) {
|
||||||
let l = 0.41;
|
let l = 0.41;
|
||||||
let jitter = rng.gen_range(-0.0..=360.0f32);
|
let jitter = rng.gen_range(-0.0..=360.0f32);
|
||||||
let h = jitter;
|
let h = jitter;
|
||||||
let color = Color::hsl(h, PLANET_SATURATION, l).as_linear_rgba_f32();
|
let color = Color::hsl(h, PLANET_SATURATION, l)
|
||||||
|
.to_linear()
|
||||||
|
.to_f32_array();
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
colors.push(color);
|
colors.push(color);
|
||||||
}
|
}
|
||||||
|
|
20
src/ui.rs
20
src/ui.rs
|
@ -1,10 +1,13 @@
|
||||||
use bevy::prelude::{
|
use avian3d::prelude::LinearVelocity;
|
||||||
AlignSelf, App, AssetServer, Color, Commands, Component, Plugin, Query, Res, Style, Text,
|
use bevy::{
|
||||||
TextBundle, TextSection, TextStyle, Transform, With,
|
app::{Startup, Update},
|
||||||
|
prelude::{
|
||||||
|
AlignSelf, App, AssetServer, Color, Commands, Component, Plugin, Query, Res, Style, Text,
|
||||||
|
TextBundle, TextSection, TextStyle, Transform, With,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "inspector")]
|
#[cfg(feature = "inspector")]
|
||||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||||
use bevy_rapier3d::prelude::Velocity;
|
|
||||||
|
|
||||||
use crate::bike::CyberBikeBody;
|
use crate::bike::CyberBikeBody;
|
||||||
|
|
||||||
|
@ -26,7 +29,7 @@ fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
style: TextStyle {
|
style: TextStyle {
|
||||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
font_size: 40.0,
|
font_size: 40.0,
|
||||||
color: Color::GOLD,
|
color: Color::srgba_u8(255, 215, 0, 230),
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -37,12 +40,12 @@ fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_ui(
|
fn update_ui(
|
||||||
state_query: Query<(&Velocity, &Transform), With<CyberBikeBody>>,
|
state_query: Query<(&LinearVelocity, &Transform), With<CyberBikeBody>>,
|
||||||
mut text_query: Query<&mut Text, With<UpText>>,
|
mut text_query: Query<&mut Text, With<UpText>>,
|
||||||
) {
|
) {
|
||||||
let mut text = text_query.single_mut();
|
let mut text = text_query.single_mut();
|
||||||
let (velocity, xform) = state_query.single();
|
let (velocity, xform) = state_query.single();
|
||||||
let speed = velocity.linvel.dot(xform.forward());
|
let speed = velocity.dot(*xform.forward());
|
||||||
text.sections[0].value = format!("spd: {:.2}", speed);
|
text.sections[0].value = format!("spd: {:.2}", speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +56,7 @@ impl Plugin for CyberUIPlugin {
|
||||||
#[cfg(feature = "inspector")]
|
#[cfg(feature = "inspector")]
|
||||||
app.add_plugin(WorldInspectorPlugin);
|
app.add_plugin(WorldInspectorPlugin);
|
||||||
|
|
||||||
app.add_startup_system(setup_ui).add_system(update_ui);
|
app.add_systems(Startup, setup_ui)
|
||||||
|
.add_systems(Update, update_ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue