Compare commits
No commits in common. "fc81b75dfe3a90dea8efa116c6fa12006a60ba1f" and "82f95cf070ab3438bba2d430e0563ddab81be118" have entirely different histories.
fc81b75dfe
...
82f95cf070
17 changed files with 1841 additions and 3268 deletions
3943
Cargo.lock
generated
3943
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
33
Cargo.toml
33
Cargo.toml
|
@ -1,28 +1,37 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cyber_rider"
|
name = "cyber_rider"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
# bevy_polyline = "0.4"
|
# bevy_polyline = "0.4"
|
||||||
noise = "0.9"
|
noise = { git = "https://github.com/Razaekel/noise-rs" }
|
||||||
hexasphere = "15"
|
hexasphere = "8"
|
||||||
#wgpu = "0.20"
|
wgpu = "0.15"
|
||||||
# bevy-inspector-egui = "0.18"
|
bevy-inspector-egui = "0.18"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
inspector = []
|
inspector = []
|
||||||
|
|
||||||
[dependencies.bevy]
|
[dependencies.bevy]
|
||||||
version = "0.15"
|
version = "0.10"
|
||||||
default-features = true
|
|
||||||
features = ["bevy_dev_tools"]
|
|
||||||
|
|
||||||
[dependencies.avian3d]
|
|
||||||
default-features = false
|
default-features = false
|
||||||
version = "0.2"
|
features = [
|
||||||
features = ["3d", "f32", "parry-f32", "debug-plugin", "default-collider", "collider-from-mesh"]
|
"bevy_gilrs",
|
||||||
|
"bevy_winit",
|
||||||
|
"png",
|
||||||
|
"hdr",
|
||||||
|
"x11",
|
||||||
|
"bevy_ui",
|
||||||
|
"bevy_text",
|
||||||
|
"bevy_gltf",
|
||||||
|
"bevy_sprite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies.bevy_rapier3d]
|
||||||
|
features = ["debug-render-3d"]
|
||||||
|
version = "0.21"
|
||||||
|
|
||||||
# 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},
|
prelude::{Component, ReflectResource, Resource, Vec3},
|
||||||
reflect::Reflect,
|
reflect::Reflect,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,6 +26,21 @@ 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 {
|
||||||
|
@ -36,8 +51,8 @@ pub struct MovementSettings {
|
||||||
impl Default for MovementSettings {
|
impl Default for MovementSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
accel: 40.0,
|
accel: 20.0,
|
||||||
gravity: 9.8,
|
gravity: 4.8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,9 +68,9 @@ pub struct CatControllerSettings {
|
||||||
impl Default for CatControllerSettings {
|
impl Default for CatControllerSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
kp: 1200.0,
|
kp: 80.0,
|
||||||
kd: 10.0,
|
kd: 10.0,
|
||||||
ki: 50.0,
|
ki: 0.4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use avian3d::prelude::{PhysicsPlugins, SubstepCount};
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
app::FixedUpdate,
|
|
||||||
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;
|
||||||
|
@ -25,14 +24,24 @@ impl Plugin for CyberActionPlugin {
|
||||||
app.init_resource::<MovementSettings>()
|
app.init_resource::<MovementSettings>()
|
||||||
.register_type::<MovementSettings>()
|
.register_type::<MovementSettings>()
|
||||||
.init_resource::<CatControllerSettings>()
|
.init_resource::<CatControllerSettings>()
|
||||||
|
.init_resource::<ActionDebugInstant>()
|
||||||
.init_resource::<CyberLean>()
|
.init_resource::<CyberLean>()
|
||||||
.register_type::<CyberLean>()
|
.register_type::<CyberLean>()
|
||||||
.register_type::<CatControllerSettings>()
|
.register_type::<CatControllerSettings>()
|
||||||
.add_plugins((PhysicsPlugins::default(), FrameTimeDiagnosticsPlugin))
|
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
|
||||||
.insert_resource(SubstepCount(24))
|
.add_startup_system(timestep_setup)
|
||||||
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
.add_systems(
|
.add_systems(
|
||||||
FixedUpdate,
|
(
|
||||||
(set_gravity, calculate_lean, apply_lean, apply_inputs).chain(),
|
gravity,
|
||||||
|
cyber_lean,
|
||||||
|
falling_cat,
|
||||||
|
input_forces,
|
||||||
|
drag,
|
||||||
|
tunnel_out,
|
||||||
|
surface_fix,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +1,20 @@
|
||||||
use std::f32::consts::{FRAC_PI_3, FRAC_PI_4};
|
use std::f32::consts::{FRAC_PI_3, FRAC_PI_4};
|
||||||
|
|
||||||
use avian3d::prelude::{AngleLimit, ExternalTorque, Gravity, LinearVelocity, RevoluteJoint};
|
use bevy::prelude::{
|
||||||
#[cfg(feature = "inspector")]
|
Commands, Entity, Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without,
|
||||||
use bevy::prelude::Gizmos;
|
};
|
||||||
use bevy::prelude::{Quat, Query, Res, ResMut, Time, Transform, Vec3, With};
|
use bevy_rapier3d::prelude::{
|
||||||
|
CollisionGroups, ExternalForce, Group, MultibodyJoint, QueryFilter, RapierConfiguration,
|
||||||
use super::{CatControllerSettings, CatControllerState, CyberLean, MovementSettings};
|
RapierContext, ReadMassProperties, TimestepMode, Velocity,
|
||||||
use crate::{
|
|
||||||
bike::{CyberBikeBody, CyberSteering, CyberWheel},
|
|
||||||
input::InputState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The gravity vector points from the cyberbike to the center of the planet.
|
#[cfg(feature = "inspector")]
|
||||||
pub(super) fn set_gravity(
|
use super::ActionDebugInstant;
|
||||||
xform: Query<&Transform, With<CyberBikeBody>>,
|
use super::{CatControllerSettings, CatControllerState, CyberLean, MovementSettings, Tunneling};
|
||||||
settings: Res<MovementSettings>,
|
use crate::{
|
||||||
mut gravity: ResMut<Gravity>,
|
bike::{CyberBikeBody, CyberSteering, CyberWheel, WheelConfig, BIKE_WHEEL_COLLISION_GROUP},
|
||||||
) {
|
input::InputState,
|
||||||
let xform = xform.single();
|
};
|
||||||
*gravity = Gravity(xform.translation.normalize() * -settings.gravity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The desired lean angle, given steering input and speed.
|
|
||||||
pub(super) fn calculate_lean(
|
|
||||||
bike_state: Query<(&LinearVelocity, &Transform), With<CyberBikeBody>>,
|
|
||||||
wheels: Query<&Transform, With<CyberWheel>>,
|
|
||||||
input: Res<InputState>,
|
|
||||||
gravity_settings: Res<MovementSettings>,
|
|
||||||
mut lean: ResMut<CyberLean>,
|
|
||||||
) {
|
|
||||||
let (velocity, xform) = bike_state.single();
|
|
||||||
let vel = velocity.dot(*xform.forward());
|
|
||||||
let v_squared = vel.powi(2);
|
|
||||||
let steering_angle = yaw_to_angle(input.yaw);
|
|
||||||
let wheels: Vec<_> = wheels.iter().map(|w| w.translation).collect();
|
|
||||||
let wheel_base = (wheels[0] - wheels[1]).length();
|
|
||||||
let radius = wheel_base / steering_angle.tan();
|
|
||||||
let gravity = gravity_settings.gravity;
|
|
||||||
let v2_r = v_squared / radius;
|
|
||||||
let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3);
|
|
||||||
|
|
||||||
if tan_theta.is_normal() {
|
|
||||||
lean.lean = tan_theta.atan().clamp(-FRAC_PI_4, FRAC_PI_4);
|
|
||||||
} else {
|
|
||||||
lean.lean = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
|
|
||||||
pub(super) fn apply_lean(
|
|
||||||
mut bike_query: Query<(&Transform, &mut ExternalTorque, &mut CatControllerState)>,
|
|
||||||
time: Res<Time>,
|
|
||||||
settings: Res<CatControllerSettings>,
|
|
||||||
lean: Res<CyberLean>,
|
|
||||||
#[cfg(feature = "inspector")] mut gizmos: Gizmos,
|
|
||||||
) {
|
|
||||||
let (xform, mut torque, mut control_vars) = bike_query.single_mut();
|
|
||||||
let world_up = xform.translation.normalize();
|
|
||||||
let rot = Quat::from_axis_angle(*xform.back(), lean.lean);
|
|
||||||
let target_up = rotate_point(&world_up, &rot).normalize();
|
|
||||||
|
|
||||||
let bike_right = xform.right();
|
|
||||||
|
|
||||||
let roll_error = bike_right.dot(target_up);
|
|
||||||
let pitch_error = world_up.dot(*xform.back());
|
|
||||||
|
|
||||||
// only try to correct roll if we're not totally vertical
|
|
||||||
if pitch_error.abs() < 0.95 {
|
|
||||||
let (derivative, integral) = control_vars.update_roll(roll_error, time.delta_secs());
|
|
||||||
let mag =
|
|
||||||
(settings.kp * roll_error) + (settings.ki * integral) + (settings.kd * derivative);
|
|
||||||
if mag.is_normal() {
|
|
||||||
let tork = mag * *xform.back();
|
|
||||||
torque.apply_torque(tork);
|
|
||||||
#[cfg(feature = "inspector")]
|
|
||||||
gizmos.arrow(
|
|
||||||
xform.translation + Vec3::Y,
|
|
||||||
xform.translation + tork + Vec3::Y,
|
|
||||||
bevy::color::Color::WHITE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply forces to the cyberbike as a result of input.
|
|
||||||
pub(super) fn apply_inputs(
|
|
||||||
settings: Res<MovementSettings>,
|
|
||||||
input: Res<InputState>,
|
|
||||||
mut wheels: Query<(&Transform, &mut ExternalTorque), With<CyberWheel>>,
|
|
||||||
mut steering: Query<&mut RevoluteJoint, With<CyberSteering>>,
|
|
||||||
) {
|
|
||||||
// brake + thrust
|
|
||||||
for (xform, mut torque) in wheels.iter_mut() {
|
|
||||||
let target = input.throttle * settings.accel;
|
|
||||||
let tork = target * *xform.left();
|
|
||||||
torque.apply_torque(tork);
|
|
||||||
}
|
|
||||||
|
|
||||||
// steering
|
|
||||||
let mut steering = steering.single_mut();
|
|
||||||
let angle = yaw_to_angle(input.yaw);
|
|
||||||
let angle = if angle.is_normal() { -angle } else { 0.0 };
|
|
||||||
let limit = AngleLimit::new(angle - 0.01, angle + 0.01);
|
|
||||||
steering.angle_limit = Some(limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn yaw_to_angle(yaw: f32) -> f32 {
|
fn yaw_to_angle(yaw: f32) -> f32 {
|
||||||
let v = yaw.powi(5) * FRAC_PI_4;
|
let v = yaw.powi(5) * FRAC_PI_4;
|
||||||
|
@ -124,3 +35,211 @@ fn rotate_point(pt: &Vec3, rot: &Quat) -> Vec3 {
|
||||||
// why does this need to be inverted???
|
// why does this need to be inverted???
|
||||||
-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.
|
||||||
|
pub(super) fn gravity(
|
||||||
|
mut query: Query<(&Transform, &mut ExternalForce), With<CyberBikeBody>>,
|
||||||
|
settings: Res<MovementSettings>,
|
||||||
|
mut rapier_config: ResMut<RapierConfiguration>,
|
||||||
|
#[cfg(feature = "inspector")] mut debug_instant: ResMut<ActionDebugInstant>,
|
||||||
|
) {
|
||||||
|
let (xform, mut forces) = query.single_mut();
|
||||||
|
|
||||||
|
#[cfg(feature = "inspectorb")]
|
||||||
|
{
|
||||||
|
if debug_instant.elapsed().as_millis() > 6000 {
|
||||||
|
dbg!(&forces);
|
||||||
|
debug_instant.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rapier_config.gravity = xform.translation.normalize() * -settings.gravity;
|
||||||
|
forces.force = Vec3::ZERO;
|
||||||
|
forces.torque = Vec3::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The desired lean angle, given steering input and speed.
|
||||||
|
pub(super) fn cyber_lean(
|
||||||
|
bike_state: Query<(&Velocity, &Transform), With<CyberBikeBody>>,
|
||||||
|
wheels: Query<&Transform, With<CyberWheel>>,
|
||||||
|
input: Res<InputState>,
|
||||||
|
gravity_settings: Res<MovementSettings>,
|
||||||
|
mut lean: ResMut<CyberLean>,
|
||||||
|
) {
|
||||||
|
let (velocity, xform) = bike_state.single();
|
||||||
|
let vel = velocity.linvel.dot(xform.forward());
|
||||||
|
let v_squared = vel.powi(2);
|
||||||
|
let steering_angle = yaw_to_angle(input.yaw);
|
||||||
|
let wheels: Vec<_> = wheels.iter().map(|w| w.translation).collect();
|
||||||
|
let wheel_base = (wheels[0] - wheels[1]).length();
|
||||||
|
let radius = wheel_base / steering_angle.tan();
|
||||||
|
let gravity = gravity_settings.gravity;
|
||||||
|
let v2_r = v_squared / radius;
|
||||||
|
let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3);
|
||||||
|
|
||||||
|
if tan_theta.is_finite() && !tan_theta.is_subnormal() {
|
||||||
|
lean.lean = tan_theta.atan().clamp(-FRAC_PI_4, FRAC_PI_4);
|
||||||
|
} else {
|
||||||
|
lean.lean = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
|
||||||
|
pub(super) fn falling_cat(
|
||||||
|
mut bike_query: Query<(&Transform, &mut ExternalForce, &mut CatControllerState)>,
|
||||||
|
time: Res<Time>,
|
||||||
|
settings: Res<CatControllerSettings>,
|
||||||
|
lean: Res<CyberLean>,
|
||||||
|
) {
|
||||||
|
let (xform, mut forces, mut control_vars) = bike_query.single_mut();
|
||||||
|
let world_up = xform.translation.normalize();
|
||||||
|
let rot = Quat::from_axis_angle(xform.back(), lean.lean);
|
||||||
|
let target_up = rotate_point(&world_up, &rot).normalize();
|
||||||
|
|
||||||
|
let bike_right = xform.right();
|
||||||
|
|
||||||
|
let roll_error = bike_right.dot(target_up);
|
||||||
|
let pitch_error = world_up.dot(xform.back());
|
||||||
|
|
||||||
|
// only try to correct roll if we're not totally vertical
|
||||||
|
if pitch_error.abs() < 0.95 {
|
||||||
|
let (derivative, integral) = control_vars.update_roll(roll_error, time.delta_seconds());
|
||||||
|
let mag =
|
||||||
|
(settings.kp * roll_error) + (settings.ki * integral) + (settings.kd * derivative);
|
||||||
|
if mag.is_finite() {
|
||||||
|
forces.torque += xform.back() * mag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply forces to the cyberbike as a result of input.
|
||||||
|
pub(super) fn input_forces(
|
||||||
|
settings: Res<MovementSettings>,
|
||||||
|
input: Res<InputState>,
|
||||||
|
mut braking_query: Query<&mut MultibodyJoint, (Without<CyberSteering>, With<CyberWheel>)>,
|
||||||
|
mut body_query: Query<
|
||||||
|
(&Transform, &mut ExternalForce),
|
||||||
|
(With<CyberBikeBody>, Without<CyberSteering>),
|
||||||
|
>,
|
||||||
|
mut steering_query: Query<&mut MultibodyJoint, With<CyberSteering>>,
|
||||||
|
) {
|
||||||
|
let (xform, mut forces) = body_query.single_mut();
|
||||||
|
|
||||||
|
// thrust
|
||||||
|
let thrust = xform.forward() * input.throttle * settings.accel;
|
||||||
|
let point = xform.translation + xform.back();
|
||||||
|
let force = ExternalForce::at_point(thrust, point, xform.translation);
|
||||||
|
*forces += force;
|
||||||
|
|
||||||
|
// brake + thrust
|
||||||
|
for mut motor in braking_query.iter_mut() {
|
||||||
|
let factor = if input.brake {
|
||||||
|
500.00
|
||||||
|
} else {
|
||||||
|
input.throttle * settings.accel
|
||||||
|
};
|
||||||
|
let speed = if input.brake { 0.0 } else { -70.0 };
|
||||||
|
motor.data = (*motor
|
||||||
|
.data
|
||||||
|
.as_revolute_mut()
|
||||||
|
.unwrap()
|
||||||
|
.set_motor_max_force(factor)
|
||||||
|
.set_motor_velocity(speed, factor))
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
// steering
|
||||||
|
let angle = yaw_to_angle(input.yaw);
|
||||||
|
let mut steering = steering_query.single_mut();
|
||||||
|
steering.data = (*steering
|
||||||
|
.data
|
||||||
|
.as_revolute_mut()
|
||||||
|
.unwrap()
|
||||||
|
.set_motor_position(-angle, 100.0, 0.5))
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Don't let the wheels get stuck underneat the planet
|
||||||
|
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,18 +1,14 @@
|
||||||
use avian3d::prelude::{
|
|
||||||
AngularDamping, AngularVelocity, CoefficientCombine, Collider, ColliderDensity,
|
|
||||||
CollisionLayers, ExternalTorque, Friction, LinearDamping, LinearVelocity, Restitution,
|
|
||||||
RigidBody, SleepingDisabled,
|
|
||||||
};
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
core::Name,
|
prelude::{AssetServer, BuildChildren, Commands, Quat, Res, SpatialBundle, Transform, Vec3},
|
||||||
prelude::{
|
scene::SceneBundle,
|
||||||
AssetServer, BuildChildren, ChildBuild, Commands, Quat, Res, Transform, Vec3, Visibility,
|
};
|
||||||
},
|
use bevy_rapier3d::prelude::{
|
||||||
scene::SceneRoot,
|
Ccd, Collider, ColliderMassProperties, CollisionGroups, Damping, ExternalForce, Friction,
|
||||||
|
ReadMassProperties, Restitution, RigidBody, Sleeping, TransformInterpolation, Velocity,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{spawn_wheels, CyberBikeBody, Meshterial, WheelConfig};
|
use super::{spawn_wheels, CyberBikeBody, Meshterial, WheelConfig, BIKE_BODY_COLLISION_GROUP};
|
||||||
use crate::{action::CatControllerState, planet::PLANET_RADIUS, ColliderGroups};
|
use crate::{action::CatControllerState, planet::PLANET_RADIUS};
|
||||||
|
|
||||||
pub(super) fn spawn_cyberbike(
|
pub(super) fn spawn_cyberbike(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
@ -23,15 +19,19 @@ pub(super) fn spawn_cyberbike(
|
||||||
let altitude = PLANET_RADIUS - 10.0;
|
let altitude = PLANET_RADIUS - 10.0;
|
||||||
|
|
||||||
let mut xform = Transform::from_translation(Vec3::X * altitude)
|
let mut xform = Transform::from_translation(Vec3::X * altitude)
|
||||||
.with_rotation(Quat::from_axis_angle(Vec3::Z, (-89.0f32).to_radians()));
|
.with_rotation(Quat::from_axis_angle(Vec3::Z, -89.0f32.to_radians()));
|
||||||
|
|
||||||
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 {
|
||||||
dynamic_coefficient: 0.1,
|
coefficient: 0.01,
|
||||||
static_coefficient: 0.6,
|
..Default::default()
|
||||||
combine_rule: CoefficientCombine::Average,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let restitution = Restitution {
|
let restitution = Restitution {
|
||||||
|
@ -39,39 +39,47 @@ pub(super) fn spawn_cyberbike(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let bike_collision_group =
|
let mass_properties = ColliderMassProperties::Density(1.5);
|
||||||
CollisionLayers::new(ColliderGroups::BikeBody, ColliderGroups::Planet);
|
|
||||||
|
let (membership, filter) = BIKE_BODY_COLLISION_GROUP;
|
||||||
|
let bike_collision_group = CollisionGroups::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 body_collider =
|
let spatialbundle = SpatialBundle {
|
||||||
Collider::capsule_endpoints(0.5, Vec3::new(0.0, 0.0, -0.65), Vec3::new(0.0, 0.0, 0.8));
|
transform: xform,
|
||||||
let spatial_bundle = (xform, Visibility::default());
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let bike = commands
|
let bike = commands
|
||||||
.spawn(spatial_bundle)
|
.spawn(RigidBody::Dynamic)
|
||||||
|
.insert(spatialbundle)
|
||||||
.insert((
|
.insert((
|
||||||
Name::new("bike body"),
|
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,
|
||||||
SleepingDisabled,
|
Sleeping::disabled(),
|
||||||
CyberBikeBody,
|
Ccd { enabled: true },
|
||||||
CatControllerState::default(),
|
ReadMassProperties::default(),
|
||||||
ColliderDensity(20.0),
|
|
||||||
LinearDamping(0.1),
|
|
||||||
AngularDamping(2.0),
|
|
||||||
LinearVelocity::ZERO,
|
|
||||||
AngularVelocity::ZERO,
|
|
||||||
ExternalTorque::ZERO.with_persistence(false),
|
|
||||||
))
|
))
|
||||||
.with_children(|rider| {
|
.insert(TransformInterpolation {
|
||||||
rider.spawn(SceneRoot(scene));
|
start: None,
|
||||||
|
end: None,
|
||||||
})
|
})
|
||||||
|
.insert(Velocity::zero())
|
||||||
|
.insert(ExternalForce::default())
|
||||||
|
.with_children(|rider| {
|
||||||
|
rider.spawn(SceneBundle {
|
||||||
|
scene,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.insert(CyberBikeBody)
|
||||||
|
.insert(CatControllerState::default())
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
bevy::log::info!("bike body: {bike:?}");
|
spawn_wheels(&mut commands, bike, &wheel_conf, &mut meshterials);
|
||||||
|
|
||||||
spawn_wheels(&mut commands, bike, &xform, &wheel_conf, &mut meshterials);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ use bevy::{
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct CyberBikeBody;
|
pub struct CyberBikeBody;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Component)]
|
#[derive(Component)]
|
||||||
pub struct CyberSteering;
|
pub struct CyberSteering;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Component)]
|
#[derive(Debug, Component)]
|
||||||
pub struct CyberWheel;
|
pub struct CyberWheel;
|
||||||
|
|
||||||
#[derive(Resource, Reflect)]
|
#[derive(Resource, Reflect)]
|
||||||
|
@ -33,15 +33,15 @@ impl Default for WheelConfig {
|
||||||
Self {
|
Self {
|
||||||
front_forward: 0.8,
|
front_forward: 0.8,
|
||||||
rear_back: 1.0,
|
rear_back: 1.0,
|
||||||
y: -0.5,
|
y: -0.1,
|
||||||
limits: [-0.5, 0.1],
|
limits: [-0.5, 0.1],
|
||||||
stiffness: 3.0,
|
stiffness: 190.0,
|
||||||
damping: 8.0,
|
damping: 8.0,
|
||||||
radius: 0.40,
|
radius: 0.25,
|
||||||
thickness: 0.15,
|
thickness: 0.11,
|
||||||
friction: 1.5,
|
friction: 1.2,
|
||||||
restitution: 0.1,
|
restitution: 0.95,
|
||||||
density: 30.0,
|
density: 0.05,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,16 @@ mod body;
|
||||||
mod components;
|
mod components;
|
||||||
mod wheels;
|
mod wheels;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::prelude::{
|
||||||
app::PostStartup,
|
App, Assets, IntoSystemConfig, Mesh, Plugin, ResMut, StandardMaterial, StartupSet,
|
||||||
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: (u32, u32) = (1, 1);
|
pub const BIKE_BODY_COLLISION_GROUP: (Group, Group) = (Group::GROUP_1, Group::GROUP_1);
|
||||||
pub const BIKE_WHEEL_COLLISION_GROUP: (u32, u32) = (2, 2);
|
pub const BIKE_WHEEL_COLLISION_GROUP: (Group, Group) = (Group::GROUP_10, Group::GROUP_10);
|
||||||
|
|
||||||
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_systems(PostStartup, spawn_cyberbike);
|
.add_startup_system(spawn_cyberbike.in_base_set(StartupSet::PostStartup));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
use avian3d::{
|
use bevy::prelude::{shape::Torus as Tire, *};
|
||||||
math::FRAC_PI_2,
|
use bevy_rapier3d::prelude::{
|
||||||
prelude::{
|
Ccd, CoefficientCombineRule, Collider, ColliderMassProperties, CollisionGroups, Damping,
|
||||||
Collider, ColliderDensity, CollisionLayers, CollisionMargin, ExternalTorque, FixedJoint,
|
ExternalForce, Friction, MultibodyJoint, PrismaticJointBuilder, Restitution,
|
||||||
Friction, Joint, MassPropertiesBundle, Restitution, RevoluteJoint, RigidBody, SweptCcd,
|
RevoluteJointBuilder, RigidBody, Sleeping, TransformInterpolation,
|
||||||
},
|
|
||||||
};
|
|
||||||
use bevy::{
|
|
||||||
pbr::MeshMaterial3d,
|
|
||||||
prelude::{
|
|
||||||
AlphaMode, Assets, Color, Commands, Entity, Mesh, Mesh3d, Name, Quat, ResMut, Sphere,
|
|
||||||
StandardMaterial, Torus, Transform, Vec3, Visibility,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{CyberSteering, CyberWheel, Meshterial, WheelConfig};
|
use super::{CyberSteering, CyberWheel, Meshterial, WheelConfig, BIKE_WHEEL_COLLISION_GROUP};
|
||||||
use crate::ColliderGroups;
|
|
||||||
|
|
||||||
pub fn spawn_wheels(
|
pub fn spawn_wheels(
|
||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
bike: Entity,
|
bike: Entity,
|
||||||
xform: &Transform,
|
|
||||||
conf: &WheelConfig,
|
conf: &WheelConfig,
|
||||||
meshterials: &mut Meshterial,
|
meshterials: &mut Meshterial,
|
||||||
) {
|
) {
|
||||||
|
let (membership, filter) = BIKE_WHEEL_COLLISION_GROUP;
|
||||||
|
let wheels_collision_group = CollisionGroups::new(membership, filter);
|
||||||
let wheel_y = conf.y;
|
let wheel_y = conf.y;
|
||||||
|
let stiffness = conf.stiffness;
|
||||||
|
let not_sleeping = Sleeping::disabled();
|
||||||
|
let ccd = Ccd { enabled: true };
|
||||||
|
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 {
|
||||||
|
coefficient: conf.friction,
|
||||||
|
combine_rule: CoefficientCombineRule::Average,
|
||||||
|
};
|
||||||
|
|
||||||
let mut wheel_poses = Vec::with_capacity(2);
|
let mut wheel_poses = Vec::with_capacity(2);
|
||||||
|
|
||||||
// front
|
// front
|
||||||
|
@ -46,102 +46,132 @@ pub fn spawn_wheels(
|
||||||
wheel_poses.push((offset, None));
|
wheel_poses.push((offset, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
// tires
|
|
||||||
let outer_radius = conf.radius;
|
|
||||||
let inner_radius = conf.radius - conf.thickness;
|
|
||||||
let mesh = Mesh::from(Torus::new(inner_radius, outer_radius))
|
|
||||||
.rotated_by(Quat::from_rotation_z(FRAC_PI_2));
|
|
||||||
let collider = Collider::convex_hull_from_mesh(&mesh).unwrap();
|
|
||||||
|
|
||||||
for (offset, steering) in wheel_poses {
|
for (offset, steering) in wheel_poses {
|
||||||
let hub = wheels_helper(
|
let (mesh, collider) = gen_tires(conf);
|
||||||
commands,
|
|
||||||
meshes,
|
|
||||||
materials,
|
|
||||||
offset + xform.translation,
|
|
||||||
mesh.clone(),
|
|
||||||
collider.clone(),
|
|
||||||
conf,
|
|
||||||
steering.is_some(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(steering) = steering {
|
let material = StandardMaterial {
|
||||||
commands.spawn((
|
base_color: Color::Rgba {
|
||||||
RevoluteJoint::new(bike, hub)
|
red: 0.01,
|
||||||
.with_aligned_axis(rake_vec)
|
green: 0.01,
|
||||||
.with_angle_limits(-0.01, 0.01)
|
blue: 0.01,
|
||||||
.with_local_anchor_2(Vec3::new(0.0, 0.08, -0.05))
|
alpha: 1.0,
|
||||||
.with_local_anchor_1(offset),
|
},
|
||||||
steering,
|
alpha_mode: AlphaMode::Opaque,
|
||||||
));
|
perceptual_roughness: 0.5,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pbr_bundle = PbrBundle {
|
||||||
|
material: materials.add(material),
|
||||||
|
mesh: meshes.add(mesh),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mass_props = ColliderMassProperties::Density(conf.density);
|
||||||
|
let suspension_damping = conf.damping;
|
||||||
|
|
||||||
|
let suspension_axis = if steering.is_some() {
|
||||||
|
rake_vec
|
||||||
} else {
|
} else {
|
||||||
commands.spawn(FixedJoint::new(bike, hub).with_local_anchor_1(offset));
|
Vec3::Y
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let suspension_joint_builder = PrismaticJointBuilder::new(suspension_axis)
|
||||||
|
.local_anchor1(offset)
|
||||||
|
.limits(limits)
|
||||||
|
.motor_position(limits[0], stiffness, suspension_damping);
|
||||||
|
let suspension_joint = MultibodyJoint::new(bike, suspension_joint_builder);
|
||||||
|
let fork_rb_entity = commands
|
||||||
|
.spawn(RigidBody::Dynamic)
|
||||||
|
.insert(suspension_joint)
|
||||||
|
.insert(not_sleeping)
|
||||||
|
.id();
|
||||||
|
|
||||||
|
let axel_parent_entity = if let Some(steering) = steering {
|
||||||
|
let neck_builder =
|
||||||
|
RevoluteJointBuilder::new(rake_vec).local_anchor1(Vec3::new(0.0, 0.0, 0.1)); // this adds another 0.1m of trail
|
||||||
|
let neck_joint = MultibodyJoint::new(fork_rb_entity, neck_builder);
|
||||||
|
let neck = commands
|
||||||
|
.spawn(RigidBody::Dynamic)
|
||||||
|
.insert(neck_joint)
|
||||||
|
.insert(steering)
|
||||||
|
.insert(not_sleeping)
|
||||||
|
.id();
|
||||||
|
neck
|
||||||
|
} else {
|
||||||
|
fork_rb_entity
|
||||||
|
};
|
||||||
|
|
||||||
|
let axel_builder = RevoluteJointBuilder::new(Vec3::X);
|
||||||
|
let axel_joint = MultibodyJoint::new(axel_parent_entity, axel_builder);
|
||||||
|
let wheel_damping = Damping {
|
||||||
|
linear_damping: 0.8,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wheels_helper(
|
// do mesh shit
|
||||||
commands: &mut Commands,
|
fn gen_tires(conf: &WheelConfig) -> (Mesh, Collider) {
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
let wheel_rad = conf.radius;
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
let tire_thickness = conf.thickness;
|
||||||
position: Vec3,
|
let tire = Tire {
|
||||||
tire_mesh: Mesh,
|
radius: wheel_rad,
|
||||||
collider: Collider,
|
ring_radius: tire_thickness,
|
||||||
conf: &WheelConfig,
|
|
||||||
is_front: bool,
|
|
||||||
) -> Entity {
|
|
||||||
let wheel_material = &StandardMaterial {
|
|
||||||
base_color: Color::srgb(0.01, 0.01, 0.01),
|
|
||||||
alpha_mode: AlphaMode::Opaque,
|
|
||||||
perceptual_roughness: 0.5,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let pos_name = if is_front { "front" } else { "rear" };
|
let mut mesh = Mesh::from(tire);
|
||||||
|
let tire_verts = mesh
|
||||||
|
.attribute(Mesh::ATTRIBUTE_POSITION)
|
||||||
|
.unwrap()
|
||||||
|
.as_float3()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
//
|
||||||
|
let v = Vec3::from_array(*v);
|
||||||
|
let m = Mat3::from_rotation_z(90.0f32.to_radians());
|
||||||
|
let p = m.mul_vec3(v);
|
||||||
|
p.to_array()
|
||||||
|
})
|
||||||
|
.collect::<Vec<[f32; 3]>>();
|
||||||
|
mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION);
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, tire_verts);
|
||||||
|
|
||||||
let xform = Transform::from_translation(position);
|
let mut idxs = Vec::new();
|
||||||
let hub_mesh: Mesh = Sphere::new(0.1).into();
|
let indices = mesh.indices().unwrap().iter().collect::<Vec<_>>();
|
||||||
|
for idx in indices.as_slice().chunks_exact(3) {
|
||||||
let hub_bundle = (
|
idxs.push([idx[0] as u32, idx[1] as u32, idx[2] as u32]);
|
||||||
Mesh3d(meshes.add(hub_mesh)),
|
}
|
||||||
MeshMaterial3d(materials.add(wheel_material.clone())),
|
let wheel_collider = Collider::convex_decomposition(
|
||||||
xform,
|
&mesh
|
||||||
Visibility::Visible,
|
.attribute(Mesh::ATTRIBUTE_POSITION)
|
||||||
|
.unwrap()
|
||||||
|
.as_float3()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|v| Vec3::from_array(*v))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
&idxs,
|
||||||
);
|
);
|
||||||
let hub = commands
|
|
||||||
.spawn((
|
|
||||||
Name::new(format!("{pos_name} hub")),
|
|
||||||
RigidBody::Dynamic,
|
|
||||||
MassPropertiesBundle::from_shape(&Collider::sphere(0.1), 1000.0),
|
|
||||||
CollisionLayers::NONE,
|
|
||||||
hub_bundle,
|
|
||||||
))
|
|
||||||
.id();
|
|
||||||
|
|
||||||
let tire_bundle = (
|
(mesh, wheel_collider)
|
||||||
Mesh3d(meshes.add(tire_mesh)),
|
|
||||||
MeshMaterial3d(materials.add(wheel_material.clone())),
|
|
||||||
xform,
|
|
||||||
Visibility::Visible,
|
|
||||||
);
|
|
||||||
let tire = commands
|
|
||||||
.spawn((
|
|
||||||
Name::new(format!("{pos_name} tire")),
|
|
||||||
tire_bundle,
|
|
||||||
CyberWheel,
|
|
||||||
RigidBody::Dynamic,
|
|
||||||
collider,
|
|
||||||
Friction::new(conf.friction),
|
|
||||||
Restitution::new(conf.restitution),
|
|
||||||
ColliderDensity(conf.density),
|
|
||||||
CollisionLayers::new(ColliderGroups::Wheels, ColliderGroups::Planet),
|
|
||||||
ExternalTorque::ZERO.with_persistence(false),
|
|
||||||
CollisionMargin(0.05),
|
|
||||||
SweptCcd::NON_LINEAR,
|
|
||||||
))
|
|
||||||
.id();
|
|
||||||
|
|
||||||
// connect hubs and tires to make wheels
|
|
||||||
commands.spawn(RevoluteJoint::new(hub, tire).with_aligned_axis(Vec3::X));
|
|
||||||
hub
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{bike::CyberBikeBody, input::InputState, ui::setup_ui};
|
use crate::{bike::CyberBikeBody, input::InputState};
|
||||||
|
|
||||||
// 85 degrees in radians
|
// 85 degrees in radians
|
||||||
const MAX_PITCH: f32 = 1.48353;
|
const MAX_PITCH: f32 = 1.48353;
|
||||||
|
@ -38,25 +38,22 @@ impl CyberCameras {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_cybercams(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn setup_cybercams(mut commands: Commands) {
|
||||||
let hero_projection = PerspectiveProjection {
|
let hero_projection = PerspectiveProjection {
|
||||||
fov: std::f32::consts::FRAC_PI_3,
|
fov: std::f32::consts::FRAC_PI_3,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let id = commands
|
commands
|
||||||
.spawn((
|
.spawn(Camera3dBundle {
|
||||||
Camera3d::default(),
|
projection: bevy::render::camera::Projection::Perspective(hero_projection),
|
||||||
bevy::render::camera::Projection::Perspective(hero_projection),
|
..Default::default()
|
||||||
))
|
})
|
||||||
.insert((CyberCameras::Hero, Msaa::Sample4))
|
.insert(CyberCameras::Hero);
|
||||||
.id();
|
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn(Camera3d::default())
|
.spawn(Camera3dBundle::default())
|
||||||
.insert((CyberCameras::Debug, Msaa::Sample4));
|
.insert(CyberCameras::Debug);
|
||||||
|
|
||||||
setup_ui(commands, asset_server, id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn follow_cyberbike(
|
fn follow_cyberbike(
|
||||||
|
@ -84,10 +81,10 @@ 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 = Transform::from_translation(bike_xform.translation);
|
let mut ncx = bike_xform.to_owned();
|
||||||
ncx.rotate(Quat::from_axis_angle(up, offset.rot.to_radians()));
|
ncx.rotate(Quat::from_axis_angle(up, offset.rot.to_radians()));
|
||||||
ncx.translation += ncx.forward() * offset.dist;
|
ncx.translation += ncx.forward() * offset.dist;
|
||||||
ncx.translation += ncx.up() * offset.alt;
|
ncx.translation += ncx.up() * offset.alt;
|
||||||
|
@ -100,15 +97,12 @@ fn follow_cyberbike(
|
||||||
|
|
||||||
fn update_active_camera(
|
fn update_active_camera(
|
||||||
state: Res<State<CyberCameras>>,
|
state: Res<State<CyberCameras>>,
|
||||||
mut cameras: Query<(Entity, &mut Camera, &CyberCameras)>,
|
mut query: Query<(&mut Camera, &CyberCameras)>,
|
||||||
mut target: Query<&mut TargetCamera>,
|
|
||||||
) {
|
) {
|
||||||
let mut target = target.single_mut();
|
|
||||||
// find the camera with the current state, set it as the ActiveCamera
|
// find the camera with the current state, set it as the ActiveCamera
|
||||||
cameras.iter_mut().for_each(|(ent, mut cam, cyber)| {
|
query.iter_mut().for_each(|(mut cam, cyber)| {
|
||||||
if cyber.eq(state.get()) {
|
if cyber.eq(&state.0) {
|
||||||
cam.is_active = true;
|
cam.is_active = true;
|
||||||
*target = TargetCamera(ent);
|
|
||||||
} else {
|
} else {
|
||||||
cam.is_active = false;
|
cam.is_active = false;
|
||||||
}
|
}
|
||||||
|
@ -118,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<ButtonInput<KeyCode>>,
|
mut keys: ResMut<Input<KeyCode>>,
|
||||||
) {
|
) {
|
||||||
if keys.just_pressed(KeyCode::KeyD) {
|
if keys.just_pressed(KeyCode::D) {
|
||||||
let new_state = state.get().next();
|
let new_state = state.0.next();
|
||||||
info!("{:?}", new_state);
|
info!("{:?}", new_state);
|
||||||
next.set(new_state);
|
next.set(new_state);
|
||||||
keys.reset(KeyCode::KeyD);
|
keys.reset(KeyCode::D);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,13 +132,9 @@ 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_systems(Startup, setup_cybercams)
|
.add_startup_system(setup_cybercams)
|
||||||
.init_state::<CyberCameras>()
|
.add_state::<CyberCameras>()
|
||||||
.add_systems(Update, (cycle_cam_state, update_active_camera))
|
.add_system(cycle_cam_state)
|
||||||
.add_systems(
|
.add_system(update_active_camera)
|
||||||
PostUpdate,
|
.add_system(follow_cyberbike);
|
||||||
follow_cyberbike
|
|
||||||
.after(avian3d::schedule::PhysicsSet::Sync)
|
|
||||||
.before(TransformSystem::TransformPropagate),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
use avian3d::debug_render::PhysicsGizmos;
|
use bevy::prelude::{App, Color, Plugin};
|
||||||
use bevy::{
|
|
||||||
color::Srgba,
|
|
||||||
gizmos::AppGizmoBuilder,
|
|
||||||
math::Vec3,
|
|
||||||
prelude::{App, Color, GizmoConfig, Plugin},
|
|
||||||
};
|
|
||||||
|
|
||||||
// use crate::planet::CyberPlanet;
|
// use crate::planet::CyberPlanet;
|
||||||
|
|
||||||
|
@ -15,18 +9,26 @@ pub struct CyberGlamorPlugin;
|
||||||
impl Plugin for CyberGlamorPlugin {
|
impl Plugin for CyberGlamorPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
{
|
{
|
||||||
let plugin = avian3d::debug_render::PhysicsDebugPlugin::default();
|
use bevy_rapier3d::render::{
|
||||||
|
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;
|
||||||
|
|
||||||
app.add_plugins(plugin).insert_gizmo_config(
|
let rplugin = RapierDebugRenderPlugin {
|
||||||
PhysicsGizmos {
|
style,
|
||||||
contact_point_color: Some(Srgba::GREEN.into()),
|
always_on_top: true,
|
||||||
contact_normal_color: Some(Srgba::WHITE.into()),
|
enabled: true,
|
||||||
hide_meshes: true,
|
mode,
|
||||||
axis_lengths: Some(Vec3::ZERO),
|
};
|
||||||
..Default::default()
|
|
||||||
},
|
app.add_plugin(rplugin);
|
||||||
GizmoConfig::default(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
48
src/input.rs
48
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<ButtonInput<KeyCode>>) {
|
fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<Input<KeyCode>>) {
|
||||||
let keyset: HashSet<_> = keys.get_pressed().collect();
|
let keyset: HashSet<_> = keys.get_pressed().collect();
|
||||||
let shifted = keyset.contains(&KeyCode::ShiftLeft) || keyset.contains(&KeyCode::ShiftRight);
|
let shifted = keyset.contains(&KeyCode::LShift) || keyset.contains(&KeyCode::RShift);
|
||||||
|
|
||||||
for key in keyset {
|
for key in keyset {
|
||||||
match key {
|
match key {
|
||||||
KeyCode::ArrowLeft => offset.rot -= 5.0,
|
KeyCode::Left => offset.rot -= 5.0,
|
||||||
KeyCode::ArrowRight => offset.rot += 5.0,
|
KeyCode::Right => offset.rot += 5.0,
|
||||||
KeyCode::ArrowUp => {
|
KeyCode::Up => {
|
||||||
if shifted {
|
if shifted {
|
||||||
offset.alt += 0.5;
|
offset.alt += 0.5;
|
||||||
} else {
|
} else {
|
||||||
offset.dist -= 0.5;
|
offset.dist -= 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::ArrowDown => {
|
KeyCode::Down => {
|
||||||
if shifted {
|
if shifted {
|
||||||
offset.alt -= 0.5;
|
offset.alt -= 0.5;
|
||||||
} else {
|
} else {
|
||||||
|
@ -41,43 +41,46 @@ fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<ButtonI
|
||||||
}
|
}
|
||||||
|
|
||||||
if keys.get_just_released().len() > 0 {
|
if keys.get_just_released().len() > 0 {
|
||||||
let unpressed =
|
let unpressed = keys.just_released(KeyCode::LShift) || keys.just_released(KeyCode::RShift);
|
||||||
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::ShiftLeft);
|
keys.press(KeyCode::LShift);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.read() {
|
for pad_event in events.iter() {
|
||||||
match pad_event {
|
match pad_event {
|
||||||
GamepadEvent::Button(button_event) => {
|
GamepadEvent::Button(button_event) => {
|
||||||
let GamepadButtonChangedEvent { button, value, .. } = button_event;
|
let GamepadButtonChangedEvent {
|
||||||
match button {
|
button_type, value, ..
|
||||||
GamepadButton::RightTrigger2 => istate.throttle = *value,
|
} = button_event;
|
||||||
GamepadButton::LeftTrigger2 => istate.throttle = -value,
|
match button_type {
|
||||||
GamepadButton::East => {
|
GamepadButtonType::RightTrigger2 => istate.throttle = *value,
|
||||||
|
GamepadButtonType::LeftTrigger2 => istate.throttle = -value,
|
||||||
|
GamepadButtonType::East => {
|
||||||
if value > &0.5 {
|
if value > &0.5 {
|
||||||
istate.brake = true;
|
istate.brake = true;
|
||||||
} else {
|
} else {
|
||||||
istate.brake = false;
|
istate.brake = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => info!("unhandled button press: {button_event:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GamepadEvent::Axis(axis_event) => {
|
GamepadEvent::Axis(axis_event) => {
|
||||||
let GamepadAxisChangedEvent { axis, value, .. } = axis_event;
|
let GamepadAxisChangedEvent {
|
||||||
match axis {
|
axis_type, value, ..
|
||||||
GamepadAxis::LeftStickX => {
|
} = axis_event;
|
||||||
|
match axis_type {
|
||||||
|
GamepadAxisType::LeftStickX => {
|
||||||
istate.yaw = *value;
|
istate.yaw = *value;
|
||||||
}
|
}
|
||||||
GamepadAxis::RightStickY => {
|
GamepadAxisType::RightStickY => {
|
||||||
istate.pitch = *value;
|
istate.pitch = *value;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => info!("unhandled axis event: {axis_event:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GamepadEvent::Connection(_) => {}
|
GamepadEvent::Connection(_) => {}
|
||||||
|
@ -89,6 +92,7 @@ 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_systems(Update, (update_input, update_debug_cam));
|
.add_system(update_input)
|
||||||
|
.add_system(update_debug_cam);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -1,4 +1,3 @@
|
||||||
use avian3d::prelude::PhysicsLayer;
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::{Query, Vec3, Window, With},
|
prelude::{Query, Vec3, Window, With},
|
||||||
window::PrimaryWindow,
|
window::PrimaryWindow,
|
||||||
|
@ -13,18 +12,10 @@ pub mod lights;
|
||||||
pub mod planet;
|
pub mod planet;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
#[derive(PhysicsLayer, Default)]
|
|
||||||
pub enum ColliderGroups {
|
|
||||||
BikeBody,
|
|
||||||
Wheels,
|
|
||||||
#[default]
|
|
||||||
Planet,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_mouse_trap(mut window: Query<&mut Window, With<PrimaryWindow>>) {
|
pub fn disable_mouse_trap(mut window: Query<&mut Window, With<PrimaryWindow>>) {
|
||||||
let mut window = window.get_single_mut().unwrap();
|
let mut window = window.get_single_mut().unwrap();
|
||||||
window.cursor_options.grab_mode = bevy::window::CursorGrabMode::None;
|
window.cursor.grab_mode = bevy::window::CursorGrabMode::None;
|
||||||
window.cursor_options.visible = true;
|
window.cursor.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_unit_vec(r: &mut impl rand::prelude::Rng) -> Vec3 {
|
pub fn random_unit_vec(r: &mut impl rand::prelude::Rng) -> Vec3 {
|
||||||
|
|
|
@ -2,10 +2,7 @@ use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};
|
||||||
|
|
||||||
use crate::planet::PLANET_RADIUS;
|
use crate::planet::PLANET_RADIUS;
|
||||||
|
|
||||||
pub const LIGHT_RANGE: f32 = 900.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,
|
||||||
|
@ -13,18 +10,18 @@ fn spawn_static_lights(
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
let pink_light = PointLight {
|
let pink_light = PointLight {
|
||||||
intensity: 1_000_000.0,
|
intensity: 1_00.0,
|
||||||
range: LIGHT_RANGE,
|
range: LIGHT_RANGE,
|
||||||
color: PINK,
|
color: Color::PINK,
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let blue_light = PointLight {
|
let blue_light = PointLight {
|
||||||
intensity: 1_000_000.0,
|
intensity: 1_000.0,
|
||||||
range: LIGHT_RANGE,
|
range: LIGHT_RANGE,
|
||||||
color: BLUE,
|
color: Color::BLUE,
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -32,7 +29,7 @@ fn spawn_static_lights(
|
||||||
|
|
||||||
commands.insert_resource(AmbientLight {
|
commands.insert_resource(AmbientLight {
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
brightness: 8_000.0,
|
brightness: 0.2,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _cascade_shadow_config = CascadeShadowConfigBuilder {
|
let _cascade_shadow_config = CascadeShadowConfigBuilder {
|
||||||
|
@ -44,47 +41,57 @@ fn spawn_static_lights(
|
||||||
|
|
||||||
// up light
|
// up light
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn(PointLightBundle {
|
||||||
Transform::from_xyz(0.0, PLANET_RADIUS + 30.0, 0.0),
|
transform: Transform::from_xyz(0.0, PLANET_RADIUS + 30.0, 0.0),
|
||||||
pink_light,
|
point_light: pink_light,
|
||||||
Visibility::Visible,
|
..Default::default()
|
||||||
))
|
})
|
||||||
.with_children(|builder| {
|
.with_children(|builder| {
|
||||||
builder.spawn((
|
builder.spawn(PbrBundle {
|
||||||
Mesh3d(meshes.add(Mesh::from(Sphere::new(10.0)))),
|
mesh: meshes.add(
|
||||||
MeshMaterial3d(materials.add(StandardMaterial {
|
Mesh::try_from(shape::Icosphere {
|
||||||
base_color: BLUE,
|
radius: 10.0,
|
||||||
emissive: PINK.into(),
|
subdivisions: 2,
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::BLUE,
|
||||||
|
emissive: Color::PINK,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
}),
|
||||||
Transform::default(),
|
..Default::default()
|
||||||
Visibility::Inherited,
|
});
|
||||||
));
|
|
||||||
});
|
});
|
||||||
// down light
|
// down light
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn(PointLightBundle {
|
||||||
Transform::from_xyz(0.0, -PLANET_RADIUS - 30.0, 0.0),
|
transform: Transform::from_xyz(0.0, -PLANET_RADIUS - 30.0, 0.0),
|
||||||
blue_light,
|
point_light: blue_light,
|
||||||
Visibility::Visible,
|
..Default::default()
|
||||||
))
|
})
|
||||||
.with_children(|builder| {
|
.with_children(|builder| {
|
||||||
builder.spawn((
|
builder.spawn(PbrBundle {
|
||||||
Mesh3d(meshes.add(Mesh::from(Sphere::new(10.0)))),
|
mesh: meshes.add(
|
||||||
MeshMaterial3d(materials.add(StandardMaterial {
|
Mesh::try_from(shape::Icosphere {
|
||||||
base_color: PINK,
|
radius: 10.0,
|
||||||
emissive: BLUE.into(),
|
subdivisions: 2,
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
material: materials.add(StandardMaterial {
|
||||||
|
base_color: Color::PINK,
|
||||||
|
emissive: Color::BLUE,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
}),
|
||||||
Transform::default(),
|
..Default::default()
|
||||||
Visibility::Inherited,
|
});
|
||||||
));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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_systems(Startup, spawn_static_lights);
|
app.add_startup_system(spawn_static_lights);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -8,11 +8,12 @@ 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::srgb(0.64, 0.745, 0.937); // a light blue sky
|
const CYBER_SKY: Color = Color::rgb(0.64, 0.745, 0.937); // a light blue sky
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
app.insert_resource(ClearColor(CYBER_SKY))
|
app.insert_resource(Msaa::Sample4)
|
||||||
|
.insert_resource(ClearColor(CYBER_SKY))
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
resolution: (2560.0, 1440.0).into(),
|
resolution: (2560.0, 1440.0).into(),
|
||||||
|
@ -20,35 +21,18 @@ fn main() {
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
.add_plugins((
|
.add_plugin(CyberPlanetPlugin)
|
||||||
CyberPlanetPlugin,
|
.add_plugin(CyberInputPlugin)
|
||||||
CyberInputPlugin,
|
.add_plugin(CyberActionPlugin)
|
||||||
CyberActionPlugin,
|
.add_plugin(CyberCamPlugin)
|
||||||
CyberCamPlugin,
|
.add_plugin(CyberSpaceLightsPlugin)
|
||||||
CyberSpaceLightsPlugin,
|
.add_plugin(CyberUIPlugin)
|
||||||
CyberUIPlugin,
|
.add_plugin(CyberBikePlugin)
|
||||||
CyberBikePlugin,
|
.add_startup_system(disable_mouse_trap)
|
||||||
#[cfg(feature = "inspector")]
|
.add_system(bevy::window::close_on_esc);
|
||||||
CyberGlamorPlugin,
|
|
||||||
))
|
#[cfg(feature = "inspector")]
|
||||||
.add_systems(Startup, disable_mouse_trap)
|
app.add_plugin(CyberGlamorPlugin);
|
||||||
.add_systems(Update, close_on_esc);
|
|
||||||
|
|
||||||
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,14 +1,12 @@
|
||||||
use avian3d::prelude::*;
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::{shape::Icosphere, *},
|
||||||
render::{
|
render::{color::Color, mesh::Indices},
|
||||||
mesh::{Indices, PrimitiveTopology},
|
|
||||||
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};
|
||||||
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
pub const PLANET_RADIUS: f32 = 4_000.0;
|
pub const PLANET_RADIUS: f32 = 4_000.0;
|
||||||
pub const PLANET_HUE: f32 = 31.0;
|
pub const PLANET_HUE: f32 = 31.0;
|
||||||
|
@ -22,38 +20,40 @@ fn spawn_planet(
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
let (mesh, shape) = gen_planet(PLANET_RADIUS);
|
//let color = Color::rgb(0.74, 0.5334, 0.176);
|
||||||
|
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 {
|
||||||
static_coefficient: 0.8,
|
coefficient: 1.2,
|
||||||
dynamic_coefficient: 0.5,
|
..Default::default()
|
||||||
combine_rule: CoefficientCombine::Average,
|
|
||||||
},
|
},
|
||||||
Restitution::new(0.8),
|
Restitution::new(0.8),
|
||||||
);
|
);
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn(PbrBundle {
|
||||||
Mesh3d(meshes.add(mesh)),
|
mesh: meshes.add(mesh),
|
||||||
MeshMaterial3d(materials.add(Color::WHITE)),
|
material: materials.add(Color::WHITE.into()),
|
||||||
Transform::default(),
|
..Default::default()
|
||||||
Visibility::Visible,
|
})
|
||||||
))
|
.insert(pbody)
|
||||||
.insert((
|
.insert(pcollide)
|
||||||
RigidBody::Static,
|
.insert(CyberPlanet);
|
||||||
pcollide,
|
|
||||||
CyberPlanet,
|
|
||||||
CollisionLayers::new(LayerMask::ALL, LayerMask::ALL),
|
|
||||||
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_systems(Startup, spawn_planet);
|
app.add_startup_system(spawn_planet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +61,10 @@ impl Plugin for CyberPlanetPlugin {
|
||||||
// utils
|
// utils
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
fn gen_planet(radius: f32) -> (Mesh, Collider) {
|
fn gen_planet(sphere: Icosphere) -> (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(79, |point| {
|
let generated = IcoSphere::new(sphere.subdivisions, |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 +90,7 @@ fn gen_planet(radius: f32) -> (Mesh, Collider) {
|
||||||
|
|
||||||
let points = noisy_points
|
let points = noisy_points
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&p| (Vec3::from_slice(&p) * radius).into())
|
.map(|&p| (Vec3::from_slice(&p) * sphere.radius).into())
|
||||||
.collect::<Vec<[f32; 3]>>();
|
.collect::<Vec<[f32; 3]>>();
|
||||||
|
|
||||||
for p in &points {
|
for p in &points {
|
||||||
|
@ -108,13 +108,10 @@ fn gen_planet(radius: f32) -> (Mesh, Collider) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let indices = Indices::U32(indices);
|
let indices = Indices::U32(indices);
|
||||||
let collider = Collider::trimesh(points.iter().map(|p| Vec3::from_slice(p)).collect(), idxs);
|
let collider = Collider::trimesh(points.iter().map(|p| Vect::from_slice(p)).collect(), idxs);
|
||||||
|
|
||||||
let mut mesh = Mesh::new(
|
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
|
||||||
PrimitiveTopology::TriangleList,
|
mesh.set_indices(Some(indices));
|
||||||
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();
|
||||||
|
@ -132,14 +129,13 @@ fn gen_planet(radius: f32) -> (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)
|
let color = Color::hsl(h, PLANET_SATURATION, l).as_linear_rgba_f32();
|
||||||
.to_linear()
|
|
||||||
.to_f32_array();
|
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
colors.push(color);
|
colors.push(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbg!(&colors.len());
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||||
|
|
||||||
(mesh, collider)
|
(mesh, collider)
|
||||||
|
|
58
src/ui.rs
58
src/ui.rs
|
@ -1,56 +1,58 @@
|
||||||
use avian3d::prelude::LinearVelocity;
|
use bevy::prelude::{
|
||||||
use bevy::{
|
AlignSelf, App, AssetServer, Color, Commands, Component, Plugin, Query, Res, Style, Text,
|
||||||
app::Update,
|
TextBundle, TextSection, TextStyle, Transform, With,
|
||||||
prelude::{
|
|
||||||
AlignSelf, App, AssetServer, Color, Commands, Component, Entity, Plugin, Query, Res, Text,
|
|
||||||
With,
|
|
||||||
},
|
|
||||||
text::{TextColor, TextFont},
|
|
||||||
ui::{Node, TargetCamera},
|
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "inspector")]
|
||||||
|
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||||
|
use bevy_rapier3d::prelude::Velocity;
|
||||||
|
|
||||||
use crate::bike::CyberBikeBody;
|
use crate::bike::CyberBikeBody;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct UpText;
|
struct UpText;
|
||||||
|
|
||||||
pub(crate) fn setup_ui(
|
fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
mut commands: Commands,
|
|
||||||
asset_server: Res<AssetServer>,
|
|
||||||
target_camera: Entity,
|
|
||||||
) {
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn(TextBundle {
|
||||||
Node {
|
style: Style {
|
||||||
align_self: AlignSelf::FlexEnd,
|
align_self: AlignSelf::FlexEnd,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Use `Text` directly
|
// Use `Text` directly
|
||||||
TextFont {
|
text: Text {
|
||||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
// Construct a `Vec` of `TextSection`s
|
||||||
font_size: 40.0,
|
sections: vec![TextSection {
|
||||||
|
value: "".to_string(),
|
||||||
|
style: TextStyle {
|
||||||
|
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
|
font_size: 40.0,
|
||||||
|
color: Color::GOLD,
|
||||||
|
},
|
||||||
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
TextColor(Color::srgba_u8(255, 215, 0, 230)),
|
..Default::default()
|
||||||
Text::default(),
|
})
|
||||||
))
|
.insert(UpText);
|
||||||
.insert((UpText, TargetCamera(target_camera)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_ui(
|
fn update_ui(
|
||||||
state_query: Query<&LinearVelocity, With<CyberBikeBody>>,
|
state_query: Query<(&Velocity, &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 = state_query.single();
|
let (velocity, xform) = state_query.single();
|
||||||
let speed = velocity.0.length();
|
let speed = velocity.linvel.dot(xform.forward());
|
||||||
text.0 = format!("spd: {:.2}", speed);
|
text.sections[0].value = format!("spd: {:.2}", speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CyberUIPlugin;
|
pub struct CyberUIPlugin;
|
||||||
|
|
||||||
impl Plugin for CyberUIPlugin {
|
impl Plugin for CyberUIPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Update, update_ui);
|
#[cfg(feature = "inspector")]
|
||||||
|
app.add_plugin(WorldInspectorPlugin);
|
||||||
|
|
||||||
|
app.add_startup_system(setup_ui).add_system(update_ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue