Compare commits

..

12 commits

Author SHA1 Message Date
Joe Ardent
146d401aff kinda working friction sim 2024-06-19 17:15:09 -07:00
Joe Ardent
c3e955c1cd actually kinda works, but needs simulated friction. 2024-06-19 16:37:40 -07:00
Joe Ardent
0fae893508 use colliders for wheels, fields for thrust and steering 2024-06-18 19:10:23 -07:00
Joe Ardent
94717dd60c unstable normal forces 2024-06-17 13:09:41 -07:00
Joe Ardent
18766a3d5b checkpoint, no normal force yet 2024-06-16 09:21:42 -07:00
Joe Ardent
e358830c5f no colliders for wheels 2024-06-15 18:54:41 -07:00
Joe Ardent
8a440d6e81 add prev vel to body 2024-06-15 15:55:55 -07:00
Joe Ardent
ff9b1671b1 checkpoint; wheel_forces skeleton fn 2024-06-15 15:43:48 -07:00
Joe Ardent
59f0ed89e9 checkpoint, frictionless wheels 2024-06-15 14:18:17 -07:00
Joe Ardent
3b307f4389 better, wheels still get mired 2024-06-13 16:29:50 -07:00
Joe Ardent
66724e8946 still not showing the color/textures 2024-06-13 14:50:41 -07:00
Joe Ardent
63ed962155 update to bevy 13 2024-06-13 13:00:39 -07:00
17 changed files with 2168 additions and 2509 deletions

3649
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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]

View file

@ -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,
} }
} }
} }

View file

@ -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(),
); );
} }
} }

View file

@ -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() {

View file

@ -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);
} }

View file

@ -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,
} }
} }
} }

View file

@ -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>>,

View file

@ -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
} }

View file

@ -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),
); );
} }

View file

@ -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(),
);
} }
} }
} }

View file

@ -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(_) => {}

View file

@ -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 {

View file

@ -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;

View file

@ -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();
}
}
}

View file

@ -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)
} }

View file

@ -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);
} }
} }