Compare commits
12 commits
main
...
scavengers
Author | SHA1 | Date | |
---|---|---|---|
|
146d401aff | ||
|
c3e955c1cd | ||
|
0fae893508 | ||
|
94717dd60c | ||
|
18766a3d5b | ||
|
e358830c5f | ||
|
8a440d6e81 | ||
|
ff9b1671b1 | ||
|
59f0ed89e9 | ||
|
3b307f4389 | ||
|
66724e8946 | ||
|
63ed962155 |
17 changed files with 2168 additions and 2509 deletions
3649
Cargo.lock
generated
3649
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
34
Cargo.toml
34
Cargo.toml
|
@ -1,28 +1,38 @@
|
||||||
[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.19"
|
||||||
# bevy-inspector-egui = "0.18"
|
bevy-inspector-egui = "0.24"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
inspector = []
|
inspector = []
|
||||||
|
|
||||||
[dependencies.bevy]
|
[dependencies.bevy]
|
||||||
version = "0.15"
|
version = "0.13"
|
||||||
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",
|
||||||
|
"tonemapping_luts"
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies.bevy_rapier3d]
|
||||||
|
features = ["debug-render-3d"]
|
||||||
|
version = "0.26"
|
||||||
|
|
||||||
# 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,9 +1,10 @@
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
use bevy_rapier3d::dynamics::Velocity;
|
||||||
|
|
||||||
#[derive(Debug, Resource)]
|
#[derive(Debug, Resource)]
|
||||||
pub(crate) struct ActionDebugInstant(pub Instant);
|
pub(crate) struct ActionDebugInstant(pub Instant);
|
||||||
|
@ -26,6 +27,9 @@ impl ActionDebugInstant {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Component, Default)]
|
||||||
|
pub struct PreviousVelocity(pub Velocity);
|
||||||
|
|
||||||
#[derive(Debug, Resource, Reflect)]
|
#[derive(Debug, Resource, Reflect)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
pub struct MovementSettings {
|
pub struct MovementSettings {
|
||||||
|
@ -36,7 +40,7 @@ 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: 9.8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,9 +57,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, Startup, Update},
|
||||||
reflect::Reflect,
|
reflect::Reflect,
|
||||||
};
|
};
|
||||||
|
use bevy_rapier3d::prelude::{NoUserData, RapierPhysicsPlugin};
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
@ -25,14 +24,23 @@ 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_plugins(RapierPhysicsPlugin::<NoUserData>::default())
|
||||||
.insert_resource(SubstepCount(24))
|
.add_systems(Startup, physics_init)
|
||||||
|
.add_plugins(FrameTimeDiagnosticsPlugin)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
FixedUpdate,
|
Update,
|
||||||
(set_gravity, calculate_lean, apply_lean, apply_inputs).chain(),
|
(
|
||||||
|
reset_external_forces,
|
||||||
|
cyber_lean,
|
||||||
|
falling_cat,
|
||||||
|
wheel_forces,
|
||||||
|
drag,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,71 @@
|
||||||
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::{
|
||||||
#[cfg(feature = "inspector")]
|
prelude::{
|
||||||
use bevy::prelude::Gizmos;
|
info, Gizmos, Local, Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without,
|
||||||
use bevy::prelude::{Quat, Query, Res, ResMut, Time, Transform, Vec3, With};
|
},
|
||||||
|
render::color::Color,
|
||||||
|
};
|
||||||
|
use bevy_rapier3d::{
|
||||||
|
geometry::Friction,
|
||||||
|
prelude::{
|
||||||
|
ExternalForce, QueryFilter, RapierConfiguration, RapierContext, ReadMassProperties,
|
||||||
|
TimestepMode, Velocity,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use super::{CatControllerSettings, CatControllerState, CyberLean, MovementSettings};
|
#[cfg(feature = "inspector")]
|
||||||
|
use super::ActionDebugInstant;
|
||||||
|
use super::{
|
||||||
|
CatControllerSettings, CatControllerState, CyberLean, MovementSettings, PreviousVelocity,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
bike::{CyberBikeBody, CyberSteering, CyberWheel},
|
bike::{CyberBikeBody, CyberSteering, CyberWheel, WheelConfig},
|
||||||
input::InputState,
|
input::InputState,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The gravity vector points from the cyberbike to the center of the planet.
|
pub(super) fn physics_init(
|
||||||
pub(super) fn set_gravity(
|
mut config: ResMut<RapierConfiguration>,
|
||||||
xform: Query<&Transform, With<CyberBikeBody>>,
|
|
||||||
settings: Res<MovementSettings>,
|
settings: Res<MovementSettings>,
|
||||||
mut gravity: ResMut<Gravity>,
|
|
||||||
) {
|
) {
|
||||||
let xform = xform.single();
|
config.timestep_mode = TimestepMode::Variable {
|
||||||
*gravity = Gravity(xform.translation.normalize() * -settings.gravity);
|
max_dt: 1.0 / 60.0,
|
||||||
|
time_scale: 1.0,
|
||||||
|
substeps: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
config.gravity = Vec3::NEG_Y * settings.gravity;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn reset_external_forces(
|
||||||
|
mut query: Query<&mut ExternalForce>,
|
||||||
|
#[cfg(feature = "inspector")] mut debug_instant: ResMut<ActionDebugInstant>,
|
||||||
|
) {
|
||||||
|
for mut force in query.iter_mut() {
|
||||||
|
#[cfg(feature = "inspector")]
|
||||||
|
if debug_instant.elapsed().as_millis() > 1000 {
|
||||||
|
info!("{force:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
force.force = Vec3::ZERO;
|
||||||
|
force.torque = Vec3::ZERO;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "inspector")]
|
||||||
|
if debug_instant.elapsed().as_millis() > 1000 {
|
||||||
|
debug_instant.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The desired lean angle, given steering input and speed.
|
/// The desired lean angle, given steering input and speed.
|
||||||
pub(super) fn calculate_lean(
|
pub(super) fn cyber_lean(
|
||||||
bike_state: Query<(&LinearVelocity, &Transform), With<CyberBikeBody>>,
|
bike_state: Query<(&Velocity, &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.dot(*xform.forward());
|
let vel = velocity.linvel.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();
|
||||||
|
@ -40,7 +75,7 @@ pub(super) fn calculate_lean(
|
||||||
let v2_r = v_squared / radius;
|
let v2_r = v_squared / radius;
|
||||||
let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3);
|
let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3);
|
||||||
|
|
||||||
if tan_theta.is_normal() {
|
if tan_theta.is_finite() && !tan_theta.is_subnormal() {
|
||||||
lean.lean = tan_theta.atan().clamp(-FRAC_PI_4, FRAC_PI_4);
|
lean.lean = tan_theta.atan().clamp(-FRAC_PI_4, FRAC_PI_4);
|
||||||
} else {
|
} else {
|
||||||
lean.lean = 0.0;
|
lean.lean = 0.0;
|
||||||
|
@ -48,15 +83,20 @@ pub(super) fn calculate_lean(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
|
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
|
||||||
pub(super) fn apply_lean(
|
pub(super) fn falling_cat(
|
||||||
mut bike_query: Query<(&Transform, &mut ExternalTorque, &mut CatControllerState)>,
|
mut bike_query: Query<(
|
||||||
|
&Transform,
|
||||||
|
&Velocity,
|
||||||
|
&mut ExternalForce,
|
||||||
|
&mut CatControllerState,
|
||||||
|
)>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
settings: Res<CatControllerSettings>,
|
settings: Res<CatControllerSettings>,
|
||||||
lean: Res<CyberLean>,
|
lean: Res<CyberLean>,
|
||||||
#[cfg(feature = "inspector")] mut gizmos: Gizmos,
|
mut count: Local<usize>,
|
||||||
) {
|
) {
|
||||||
let (xform, mut torque, mut control_vars) = bike_query.single_mut();
|
let (xform, vel, mut forces, mut control_vars) = bike_query.single_mut();
|
||||||
let world_up = xform.translation.normalize();
|
let world_up = Vec3::Y; //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();
|
||||||
|
|
||||||
|
@ -65,46 +105,134 @@ pub(super) fn apply_lean(
|
||||||
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());
|
||||||
|
|
||||||
|
if *count < 30 {
|
||||||
|
if *count == 0 {
|
||||||
|
//dbg!(&xform.translation);
|
||||||
|
//dbg!(&vel.linvel);
|
||||||
|
}
|
||||||
|
*count += 1;
|
||||||
|
} else {
|
||||||
|
*count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
let (derivative, integral) = control_vars.update_roll(roll_error, time.delta_secs());
|
let (derivative, integral) = control_vars.update_roll(roll_error, time.delta_seconds());
|
||||||
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_normal() {
|
if mag.is_finite() {
|
||||||
let tork = mag * *xform.back();
|
forces.torque += xform.back() * mag;
|
||||||
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 wheel_forces(
|
||||||
pub(super) fn apply_inputs(
|
mut wheels_query: Query<
|
||||||
|
(
|
||||||
|
&Transform,
|
||||||
|
&Velocity,
|
||||||
|
&mut ExternalForce,
|
||||||
|
&mut Friction,
|
||||||
|
Option<&CyberSteering>,
|
||||||
|
),
|
||||||
|
With<CyberWheel>,
|
||||||
|
>,
|
||||||
|
wheel_config: Res<WheelConfig>,
|
||||||
|
context: Res<RapierContext>,
|
||||||
settings: Res<MovementSettings>,
|
settings: Res<MovementSettings>,
|
||||||
input: Res<InputState>,
|
input: Res<InputState>,
|
||||||
mut wheels: Query<(&Transform, &mut ExternalTorque), With<CyberWheel>>,
|
mut gizmos: Gizmos,
|
||||||
mut steering: Query<&mut RevoluteJoint, With<CyberSteering>>,
|
|
||||||
) {
|
) {
|
||||||
// brake + thrust
|
// wheel stuff
|
||||||
for (xform, mut torque) in wheels.iter_mut() {
|
let radius = wheel_config.radius;
|
||||||
let target = input.throttle * settings.accel;
|
|
||||||
let tork = target * *xform.left();
|
|
||||||
torque.apply_torque(tork);
|
|
||||||
}
|
|
||||||
|
|
||||||
// steering
|
// inputs
|
||||||
let mut steering = steering.single_mut();
|
let yaw = input.yaw;
|
||||||
let angle = yaw_to_angle(input.yaw);
|
let throttle = input.throttle * settings.accel;
|
||||||
let angle = if angle.is_normal() { -angle } else { 0.0 };
|
|
||||||
let limit = AngleLimit::new(angle - 0.01, angle + 0.01);
|
for (xform, velocity, mut external_force, mut friction, steering) in wheels_query.iter_mut() {
|
||||||
steering.angle_limit = Some(limit);
|
let pos = xform.translation;
|
||||||
|
if let Some((_, projected)) = context.project_point(pos, false, QueryFilter::only_fixed()) {
|
||||||
|
let mut force = Vec3::ZERO;
|
||||||
|
|
||||||
|
gizmos.sphere(projected.point, Quat::IDENTITY, radius, Color::RED);
|
||||||
|
|
||||||
|
let dir = (projected.point - pos).normalize();
|
||||||
|
|
||||||
|
if let Some((_, intersection)) = context.cast_ray_and_get_normal(
|
||||||
|
pos,
|
||||||
|
dir,
|
||||||
|
radius * 1.05,
|
||||||
|
false,
|
||||||
|
QueryFilter::only_fixed(),
|
||||||
|
) {
|
||||||
|
let norm = intersection.normal;
|
||||||
|
let point = intersection.point;
|
||||||
|
|
||||||
|
// do thrust's share
|
||||||
|
let thrust = norm.cross(xform.right().into()) * throttle;
|
||||||
|
force += thrust;
|
||||||
|
|
||||||
|
// do steering's share
|
||||||
|
if steering.is_some() {
|
||||||
|
let thrust = norm.cross(xform.back().into());
|
||||||
|
force += 5.0 * yaw * thrust;
|
||||||
|
}
|
||||||
|
|
||||||
|
let forward = if steering.is_some() {
|
||||||
|
let angle = yaw_to_angle(yaw);
|
||||||
|
let rot = Quat::from_axis_angle(Vec3::Y, angle);
|
||||||
|
Transform::from_rotation(rot).forward()
|
||||||
|
} else {
|
||||||
|
xform.forward()
|
||||||
|
};
|
||||||
|
|
||||||
|
// do the friction's share
|
||||||
|
//
|
||||||
|
// first the brakes
|
||||||
|
if input.brake {
|
||||||
|
friction.coefficient = 2.0;
|
||||||
|
} else {
|
||||||
|
friction.coefficient = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now static simulated friction
|
||||||
|
|
||||||
|
let alignment = forward.dot(velocity.linvel);
|
||||||
|
let alignment = if alignment.is_normal() || alignment == 0.0 {
|
||||||
|
alignment
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
dbg!(alignment);
|
||||||
|
let sign = alignment.signum();
|
||||||
|
let slide = sign * (1.0 - alignment.abs());
|
||||||
|
let perp = forward.cross(norm).normalize();
|
||||||
|
force += slide * perp;
|
||||||
|
|
||||||
|
// show the force
|
||||||
|
#[cfg(feature = "inspector")]
|
||||||
|
gizmos.arrow(pos, pos + force, Color::RED);
|
||||||
|
|
||||||
|
// ok apply the force
|
||||||
|
*external_force = ExternalForce::at_point(force, point, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helpers
|
||||||
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;
|
||||||
if v.is_normal() {
|
if v.is_normal() {
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
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, PreviousVelocity},
|
||||||
|
planet::PLANET_RADIUS,
|
||||||
|
};
|
||||||
|
|
||||||
pub(super) fn spawn_cyberbike(
|
pub(super) fn spawn_cyberbike(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
@ -20,18 +19,21 @@ pub(super) fn spawn_cyberbike(
|
||||||
wheel_conf: Res<WheelConfig>,
|
wheel_conf: Res<WheelConfig>,
|
||||||
mut meshterials: Meshterial,
|
mut meshterials: Meshterial,
|
||||||
) {
|
) {
|
||||||
let altitude = PLANET_RADIUS - 10.0;
|
let altitude = 3.0;
|
||||||
|
|
||||||
let mut xform = Transform::from_translation(Vec3::X * altitude)
|
let mut xform = Transform::from_translation(Vec3::Y * altitude);
|
||||||
.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 +41,48 @@ 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),
|
PreviousVelocity::default(),
|
||||||
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)]
|
||||||
|
@ -22,7 +22,6 @@ pub struct WheelConfig {
|
||||||
pub stiffness: f32,
|
pub stiffness: f32,
|
||||||
pub damping: f32,
|
pub damping: f32,
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
pub thickness: f32,
|
|
||||||
pub friction: f32,
|
pub friction: f32,
|
||||||
pub restitution: f32,
|
pub restitution: f32,
|
||||||
pub density: f32,
|
pub density: f32,
|
||||||
|
@ -33,15 +32,14 @@ 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: 30.0,
|
||||||
damping: 8.0,
|
damping: 8.0,
|
||||||
radius: 0.40,
|
radius: 0.3,
|
||||||
thickness: 0.15,
|
friction: 0.0,
|
||||||
friction: 1.5,
|
restitution: 0.95,
|
||||||
restitution: 0.1,
|
density: 0.05,
|
||||||
density: 30.0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
mod body;
|
mod body;
|
||||||
mod components;
|
mod components;
|
||||||
|
//mod controller;
|
||||||
mod wheels;
|
mod wheels;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::prelude::{App, Assets, Mesh, Plugin, PostStartup, ResMut, StandardMaterial};
|
||||||
app::PostStartup,
|
use bevy_rapier3d::prelude::Group;
|
||||||
prelude::{App, Assets, Mesh, Plugin, ResMut, StandardMaterial},
|
|
||||||
};
|
|
||||||
|
|
||||||
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_2, Group::GROUP_2);
|
||||||
|
|
||||||
type Meshterial<'a> = (
|
type Meshterial<'a> = (
|
||||||
ResMut<'a, Assets<Mesh>>,
|
ResMut<'a, Assets<Mesh>>,
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
use avian3d::{
|
use bevy::prelude::*;
|
||||||
math::FRAC_PI_2,
|
use bevy_rapier3d::{
|
||||||
|
dynamics::{Ccd, FixedJointBuilder, Velocity},
|
||||||
|
geometry::{Collider, CollisionGroups, Friction},
|
||||||
prelude::{
|
prelude::{
|
||||||
Collider, ColliderDensity, CollisionLayers, CollisionMargin, ExternalTorque, FixedJoint,
|
ColliderMassProperties, ExternalForce, MultibodyJoint, PrismaticJointBuilder, RigidBody,
|
||||||
Friction, Joint, MassPropertiesBundle, Restitution, RevoluteJoint, RigidBody, SweptCcd,
|
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;
|
use crate::action::PreviousVelocity;
|
||||||
|
|
||||||
pub fn spawn_wheels(
|
pub(crate) 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 wheel_y = conf.y;
|
let wheel_y = conf.y;
|
||||||
|
let stiffness = conf.stiffness;
|
||||||
|
let not_sleeping = Sleeping::disabled();
|
||||||
|
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 mut wheel_poses = Vec::with_capacity(2);
|
let mut wheel_poses = Vec::with_capacity(2);
|
||||||
|
|
||||||
|
let (membership, filter) = BIKE_WHEEL_COLLISION_GROUP;
|
||||||
|
let wheels_collision_group = CollisionGroups::new(membership, filter);
|
||||||
|
|
||||||
// front
|
// front
|
||||||
{
|
{
|
||||||
let wheel_x = 0.0;
|
let wheel_x = 0.0;
|
||||||
|
@ -46,102 +46,84 @@ 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 = 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::YELLOW,
|
||||||
RevoluteJoint::new(bike, hub)
|
alpha_mode: AlphaMode::Opaque,
|
||||||
.with_aligned_axis(rake_vec)
|
perceptual_roughness: 0.1,
|
||||||
.with_angle_limits(-0.01, 0.01)
|
..Default::default()
|
||||||
.with_local_anchor_2(Vec3::new(0.0, 0.08, -0.05))
|
};
|
||||||
.with_local_anchor_1(offset),
|
|
||||||
steering,
|
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
|
||||||
|
Vec3::Y
|
||||||
} 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 steering.is_some() {
|
||||||
|
let neck_builder = FixedJointBuilder::new().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).id();
|
||||||
|
neck
|
||||||
|
} else {
|
||||||
|
fork_rb_entity
|
||||||
|
};
|
||||||
|
|
||||||
|
let axel_builder = FixedJointBuilder::new();
|
||||||
|
let axel_joint = MultibodyJoint::new(axel_parent_entity, axel_builder);
|
||||||
|
|
||||||
|
let c = &mut commands.spawn(pbr_bundle);
|
||||||
|
c.insert((
|
||||||
|
mass_props,
|
||||||
|
axel_joint,
|
||||||
|
CyberWheel,
|
||||||
|
not_sleeping,
|
||||||
|
Velocity::default(),
|
||||||
|
PreviousVelocity::default(),
|
||||||
|
ExternalForce::default(),
|
||||||
|
SpatialBundle::default(),
|
||||||
|
TransformInterpolation::default(),
|
||||||
|
RigidBody::Dynamic,
|
||||||
|
Ccd { enabled: true },
|
||||||
|
Friction {
|
||||||
|
coefficient: 0.0,
|
||||||
|
combine_rule: bevy_rapier3d::dynamics::CoefficientCombineRule::Min,
|
||||||
|
},
|
||||||
|
wheels_collision_group,
|
||||||
|
Collider::ball(conf.radius),
|
||||||
|
));
|
||||||
|
if steering.is_some() {
|
||||||
|
c.insert(CyberSteering);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wheels_helper(
|
// do mesh shit
|
||||||
commands: &mut Commands,
|
fn gen_tires(conf: &WheelConfig) -> Mesh {
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
let wheel_rad = conf.radius;
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
let tire = Sphere::new(wheel_rad);
|
||||||
position: Vec3,
|
Mesh::from(tire)
|
||||||
tire_mesh: Mesh,
|
|
||||||
collider: Collider,
|
|
||||||
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()
|
|
||||||
};
|
|
||||||
|
|
||||||
let pos_name = if is_front { "front" } else { "rear" };
|
|
||||||
|
|
||||||
let xform = Transform::from_translation(position);
|
|
||||||
let hub_mesh: Mesh = Sphere::new(0.1).into();
|
|
||||||
|
|
||||||
let hub_bundle = (
|
|
||||||
Mesh3d(meshes.add(hub_mesh)),
|
|
||||||
MeshMaterial3d(materials.add(wheel_material.clone())),
|
|
||||||
xform,
|
|
||||||
Visibility::Visible,
|
|
||||||
);
|
|
||||||
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 = (
|
|
||||||
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(
|
||||||
|
@ -70,7 +67,7 @@ fn follow_cyberbike(
|
||||||
offset: Res<DebugCamOffset>,
|
offset: Res<DebugCamOffset>,
|
||||||
) {
|
) {
|
||||||
let bike_xform = *query.p0().single();
|
let bike_xform = *query.p0().single();
|
||||||
let up = bike_xform.translation.normalize();
|
let up = Vec3::Y; //bike_xform.translation.normalize();
|
||||||
|
|
||||||
for (mut cam_xform, cam_type) in query.p1().iter_mut() {
|
for (mut cam_xform, cam_type) in query.p1().iter_mut() {
|
||||||
match *cam_type {
|
match *cam_type {
|
||||||
|
@ -87,7 +84,7 @@ fn follow_cyberbike(
|
||||||
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.get()) {
|
||||||
cam.is_active = true;
|
cam.is_active = true;
|
||||||
*target = TargetCamera(ent);
|
|
||||||
} else {
|
} else {
|
||||||
cam.is_active = false;
|
cam.is_active = false;
|
||||||
}
|
}
|
||||||
|
@ -140,11 +134,8 @@ fn common(app: &mut bevy::prelude::App) {
|
||||||
app.insert_resource(DebugCamOffset::default())
|
app.insert_resource(DebugCamOffset::default())
|
||||||
.add_systems(Startup, setup_cybercams)
|
.add_systems(Startup, setup_cybercams)
|
||||||
.init_state::<CyberCameras>()
|
.init_state::<CyberCameras>()
|
||||||
.add_systems(Update, (cycle_cam_state, update_active_camera))
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
Update,
|
||||||
follow_cyberbike
|
(cycle_cam_state, update_active_camera, 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,27 @@ 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::COLLIDER_SHAPES
|
||||||
|
| 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_plugins(rplugin);
|
||||||
GizmoConfig::default(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
src/input.rs
26
src/input.rs
|
@ -54,30 +54,34 @@ fn update_input(mut events: EventReader<GamepadEvent>, mut istate: ResMut<InputS
|
||||||
for pad_event in events.read() {
|
for pad_event in events.read() {
|
||||||
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(_) => {}
|
||||||
|
|
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 {
|
||||||
|
|
|
@ -4,82 +4,38 @@ use crate::planet::PLANET_RADIUS;
|
||||||
|
|
||||||
pub const LIGHT_RANGE: f32 = 900.0;
|
pub const LIGHT_RANGE: f32 = 900.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>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
let pink_light = PointLight {
|
let pink_light = PointLight {
|
||||||
intensity: 1_000_000.0,
|
intensity: 10_00.0,
|
||||||
range: LIGHT_RANGE,
|
range: LIGHT_RANGE,
|
||||||
color: PINK,
|
color: Color::WHITE,
|
||||||
radius: 1.0,
|
radius: 10.0,
|
||||||
shadows_enabled: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let blue_light = PointLight {
|
|
||||||
intensity: 1_000_000.0,
|
|
||||||
range: LIGHT_RANGE,
|
|
||||||
color: BLUE,
|
|
||||||
radius: 1.0,
|
|
||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
commands.insert_resource(AmbientLight {
|
commands.insert_resource(AmbientLight {
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
brightness: 8_000.0,
|
brightness: 100.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let _cascade_shadow_config = CascadeShadowConfigBuilder {
|
// let _cascade_shadow_config = CascadeShadowConfigBuilder {
|
||||||
first_cascade_far_bound: 0.3,
|
// first_cascade_far_bound: 0.3,
|
||||||
maximum_distance: 3.0,
|
// maximum_distance: 3.0,
|
||||||
..default()
|
// ..default()
|
||||||
}
|
// }
|
||||||
.build();
|
// .build();
|
||||||
|
|
||||||
// up light
|
// up light
|
||||||
commands
|
commands.spawn(PointLightBundle {
|
||||||
.spawn((
|
transform: Transform::from_xyz(0.0, 100.0, 0.0),
|
||||||
Transform::from_xyz(0.0, PLANET_RADIUS + 30.0, 0.0),
|
point_light: pink_light,
|
||||||
pink_light,
|
..Default::default()
|
||||||
Visibility::Visible,
|
});
|
||||||
))
|
|
||||||
.with_children(|builder| {
|
|
||||||
builder.spawn((
|
|
||||||
Mesh3d(meshes.add(Mesh::from(Sphere::new(10.0)))),
|
|
||||||
MeshMaterial3d(materials.add(StandardMaterial {
|
|
||||||
base_color: BLUE,
|
|
||||||
emissive: PINK.into(),
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
Transform::default(),
|
|
||||||
Visibility::Inherited,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
// down light
|
|
||||||
commands
|
|
||||||
.spawn((
|
|
||||||
Transform::from_xyz(0.0, -PLANET_RADIUS - 30.0, 0.0),
|
|
||||||
blue_light,
|
|
||||||
Visibility::Visible,
|
|
||||||
))
|
|
||||||
.with_children(|builder| {
|
|
||||||
builder.spawn((
|
|
||||||
Mesh3d(meshes.add(Mesh::from(Sphere::new(10.0)))),
|
|
||||||
MeshMaterial3d(materials.add(StandardMaterial {
|
|
||||||
base_color: PINK,
|
|
||||||
emissive: BLUE.into(),
|
|
||||||
..Default::default()
|
|
||||||
})),
|
|
||||||
Transform::default(),
|
|
||||||
Visibility::Inherited,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CyberSpaceLightsPlugin;
|
pub struct CyberSpaceLightsPlugin;
|
||||||
|
|
28
src/main.rs
28
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(),
|
||||||
|
@ -28,27 +29,12 @@ fn main() {
|
||||||
CyberSpaceLightsPlugin,
|
CyberSpaceLightsPlugin,
|
||||||
CyberUIPlugin,
|
CyberUIPlugin,
|
||||||
CyberBikePlugin,
|
CyberBikePlugin,
|
||||||
#[cfg(feature = "inspector")]
|
|
||||||
CyberGlamorPlugin,
|
|
||||||
))
|
))
|
||||||
.add_systems(Startup, disable_mouse_trap)
|
.add_systems(Startup, disable_mouse_trap)
|
||||||
.add_systems(Update, close_on_esc);
|
.add_systems(Update, bevy::window::close_on_esc);
|
||||||
|
|
||||||
|
#[cfg(feature = "inspector")]
|
||||||
|
app.add_plugins(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
139
src/planet.rs
139
src/planet.rs
|
@ -1,16 +1,8 @@
|
||||||
use avian3d::prelude::*;
|
use bevy::{prelude::*, render::color::Color};
|
||||||
use bevy::{
|
use bevy_rapier3d::prelude::*;
|
||||||
prelude::*,
|
// use noise::{HybridMulti, NoiseFn, SuperSimplex};
|
||||||
render::{
|
|
||||||
mesh::{Indices, PrimitiveTopology},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use hexasphere::shapes::IcoSphere;
|
|
||||||
use noise::{HybridMulti, NoiseFn, SuperSimplex};
|
|
||||||
use rand::{Rng, SeedableRng};
|
|
||||||
|
|
||||||
pub const PLANET_RADIUS: f32 = 4_000.0;
|
pub const PLANET_RADIUS: f32 = 2_000.0;
|
||||||
pub const PLANET_HUE: f32 = 31.0;
|
pub const PLANET_HUE: f32 = 31.0;
|
||||||
pub const PLANET_SATURATION: f32 = 1.0;
|
pub const PLANET_SATURATION: f32 = 1.0;
|
||||||
|
|
||||||
|
@ -22,32 +14,37 @@ 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 (mesh, shape) = gen_planet(2999.9);
|
||||||
|
|
||||||
|
let pbody = (RigidBody::Fixed, Transform::from_xyz(0.0, 0.1, 0.0));
|
||||||
|
|
||||||
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),
|
||||||
|
Transform::from_xyz(0.0, 1.0, 0.0),
|
||||||
|
CollisionGroups::default(), // all colliders
|
||||||
|
Ccd { enabled: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn(PbrBundle {
|
||||||
Mesh3d(meshes.add(mesh)),
|
mesh: meshes.add(mesh),
|
||||||
MeshMaterial3d(materials.add(Color::WHITE)),
|
material: materials.add(StandardMaterial {
|
||||||
Transform::default(),
|
base_color: Color::GREEN,
|
||||||
Visibility::Visible,
|
..Default::default()
|
||||||
))
|
}),
|
||||||
.insert((
|
transform: Transform::from_xyz(0.0, 0.1, 0.0),
|
||||||
RigidBody::Static,
|
..Default::default()
|
||||||
pcollide,
|
})
|
||||||
CyberPlanet,
|
.insert(pbody)
|
||||||
CollisionLayers::new(LayerMask::ALL, LayerMask::ALL),
|
.insert(pcollide)
|
||||||
CollisionMargin(0.2),
|
.insert(CyberPlanet);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CyberPlanetPlugin;
|
pub struct CyberPlanetPlugin;
|
||||||
|
@ -61,86 +58,10 @@ impl Plugin for CyberPlanetPlugin {
|
||||||
// utils
|
// utils
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
fn gen_planet(radius: f32) -> (Mesh, Collider) {
|
fn gen_planet(span: f32) -> (Mesh, Collider) {
|
||||||
// straight-up stolen from Bevy's impl of Mesh from Icosphere, so I can do the
|
let mut mesh = Cuboid::new(span, 1.0, span);
|
||||||
// displacement before normals are calculated.
|
|
||||||
let generated = IcoSphere::new(79, |point| {
|
|
||||||
let inclination = point.y.acos();
|
|
||||||
let azimuth = point.z.atan2(point.x);
|
|
||||||
|
|
||||||
let norm_inclination = inclination / std::f32::consts::PI;
|
let collider = Collider::cuboid(span / 2.0, 0.5, span / 2.0);
|
||||||
let norm_azimuth = 0.5 - (azimuth / std::f32::consts::TAU);
|
|
||||||
|
|
||||||
[norm_azimuth, norm_inclination]
|
(mesh.mesh(), collider)
|
||||||
});
|
|
||||||
|
|
||||||
let noise = HybridMulti::<SuperSimplex>::default();
|
|
||||||
|
|
||||||
let (mut min, mut max) = (f32::MAX, f32::MIN);
|
|
||||||
|
|
||||||
let noisy_points = generated
|
|
||||||
.raw_points()
|
|
||||||
.iter()
|
|
||||||
.map(|&p| {
|
|
||||||
let disp = (noise.get(p.as_dvec3().into()) * 0.03f64) as f32;
|
|
||||||
let pt = p + (p.normalize() * disp);
|
|
||||||
pt.into()
|
|
||||||
})
|
|
||||||
.collect::<Vec<[f32; 3]>>();
|
|
||||||
|
|
||||||
let points = noisy_points
|
|
||||||
.iter()
|
|
||||||
.map(|&p| (Vec3::from_slice(&p) * radius).into())
|
|
||||||
.collect::<Vec<[f32; 3]>>();
|
|
||||||
|
|
||||||
for p in &points {
|
|
||||||
let v = Vec3::new(p[0], p[1], p[2]);
|
|
||||||
let v = v.length();
|
|
||||||
min = v.min(min);
|
|
||||||
max = v.max(max);
|
|
||||||
}
|
|
||||||
|
|
||||||
let indices = generated.get_all_indices();
|
|
||||||
|
|
||||||
let mut idxs = Vec::new();
|
|
||||||
for idx in indices.chunks_exact(3) {
|
|
||||||
idxs.push([idx[0], idx[1], idx[2]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let indices = Indices::U32(indices);
|
|
||||||
let collider = Collider::trimesh(points.iter().map(|p| Vec3::from_slice(p)).collect(), idxs);
|
|
||||||
|
|
||||||
let mut mesh = Mesh::new(
|
|
||||||
PrimitiveTopology::TriangleList,
|
|
||||||
RenderAssetUsages::default(),
|
|
||||||
);
|
|
||||||
mesh.insert_indices(indices);
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, points);
|
|
||||||
//mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
|
||||||
mesh.duplicate_vertices();
|
|
||||||
mesh.compute_flat_normals();
|
|
||||||
|
|
||||||
let tri_list = mesh
|
|
||||||
.attribute(Mesh::ATTRIBUTE_POSITION)
|
|
||||||
.unwrap()
|
|
||||||
.as_float3()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(57);
|
|
||||||
let mut colors = Vec::new();
|
|
||||||
for _triangle in tri_list.chunks_exact(3) {
|
|
||||||
let l = 0.41;
|
|
||||||
let jitter = rng.gen_range(-0.0..=360.0f32);
|
|
||||||
let h = jitter;
|
|
||||||
let color = Color::hsl(h, PLANET_SATURATION, l)
|
|
||||||
.to_linear()
|
|
||||||
.to_f32_array();
|
|
||||||
for _ in 0..3 {
|
|
||||||
colors.push(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
|
||||||
|
|
||||||
(mesh, collider)
|
|
||||||
}
|
}
|
||||||
|
|
52
src/ui.rs
52
src/ui.rs
|
@ -1,56 +1,58 @@
|
||||||
use avian3d::prelude::LinearVelocity;
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
app::Update,
|
app::{Startup, Update},
|
||||||
prelude::{
|
prelude::{
|
||||||
AlignSelf, App, AssetServer, Color, Commands, Component, Entity, Plugin, Query, Res, Text,
|
AlignSelf, App, AssetServer, Color, Commands, Component, Plugin, Query, Res, Style, Text,
|
||||||
With,
|
TextBundle, TextSection, TextStyle, Transform, With,
|
||||||
},
|
},
|
||||||
text::{TextColor, TextFont},
|
|
||||||
ui::{Node, TargetCamera},
|
|
||||||
};
|
};
|
||||||
|
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);
|
//
|
||||||
|
app.add_systems(Startup, setup_ui)
|
||||||
|
.add_systems(Update, update_ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue