Compare commits

..

No commits in common. "scavengers" and "main" have entirely different histories.

16 changed files with 1433 additions and 2519 deletions

3014
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ rand = "0.8"
# bevy_polyline = "0.4"
noise = { git = "https://github.com/Razaekel/noise-rs" }
hexasphere = "8"
wgpu = "0.19"
bevy-inspector-egui = "0.24"
wgpu = "0.15"
bevy-inspector-egui = "0.18"
[features]
inspector = []
[dependencies.bevy]
version = "0.13"
version = "0.10"
default-features = false
features = [
"bevy_gilrs",
@ -27,12 +27,11 @@ features = [
"bevy_text",
"bevy_gltf",
"bevy_sprite",
"tonemapping_luts"
]
[dependencies.bevy_rapier3d]
features = ["debug-render-3d"]
version = "0.26"
version = "0.21"
# Maybe also enable only a small amount of optimization for our code:
[profile.dev]

View File

@ -4,7 +4,6 @@ use bevy::{
prelude::{Component, ReflectResource, Resource, Vec3},
reflect::Reflect,
};
use bevy_rapier3d::dynamics::Velocity;
#[derive(Debug, Resource)]
pub(crate) struct ActionDebugInstant(pub Instant);
@ -27,8 +26,20 @@ impl ActionDebugInstant {
}
}
#[derive(Debug, Component, Default)]
pub struct PreviousVelocity(pub Velocity);
#[derive(Debug, Component)]
pub(super) struct Tunneling {
pub frames: usize,
pub dir: Vec3,
}
impl Default for Tunneling {
fn default() -> Self {
Tunneling {
frames: 15,
dir: Vec3::ZERO,
}
}
}
#[derive(Debug, Resource, Reflect)]
#[reflect(Resource)]
@ -41,7 +52,7 @@ impl Default for MovementSettings {
fn default() -> Self {
Self {
accel: 20.0,
gravity: 9.8,
gravity: 4.8,
}
}
}

View File

@ -1,7 +1,7 @@
use bevy::{
diagnostic::FrameTimeDiagnosticsPlugin,
ecs::reflect::ReflectResource,
prelude::{App, IntoSystemConfigs, Plugin, Resource, Startup, Update},
prelude::{App, IntoSystemConfigs, Plugin, Resource},
reflect::Reflect,
};
use bevy_rapier3d::prelude::{NoUserData, RapierPhysicsPlugin};
@ -28,17 +28,18 @@ impl Plugin for CyberActionPlugin {
.init_resource::<CyberLean>()
.register_type::<CyberLean>()
.register_type::<CatControllerSettings>()
.add_plugins(RapierPhysicsPlugin::<NoUserData>::default())
.add_systems(Startup, physics_init)
.add_plugins(FrameTimeDiagnosticsPlugin)
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
.add_startup_system(timestep_setup)
.add_plugin(FrameTimeDiagnosticsPlugin::default())
.add_systems(
Update,
(
reset_external_forces,
gravity,
cyber_lean,
falling_cat,
wheel_forces,
input_forces,
drag,
tunnel_out,
surface_fix,
)
.chain(),
);

View File

@ -1,238 +1,21 @@
use std::f32::consts::{FRAC_PI_3, FRAC_PI_4};
use bevy::{
prelude::{
info, Gizmos, Local, Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without,
},
render::color::Color,
use bevy::prelude::{
Commands, Entity, Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without,
};
use bevy_rapier3d::{
geometry::Friction,
prelude::{
ExternalForce, QueryFilter, RapierConfiguration, RapierContext, ReadMassProperties,
TimestepMode, Velocity,
},
use bevy_rapier3d::prelude::{
CollisionGroups, ExternalForce, Group, MultibodyJoint, QueryFilter, RapierConfiguration,
RapierContext, ReadMassProperties, TimestepMode, Velocity,
};
#[cfg(feature = "inspector")]
use super::ActionDebugInstant;
use super::{
CatControllerSettings, CatControllerState, CyberLean, MovementSettings, PreviousVelocity,
};
use super::{CatControllerSettings, CatControllerState, CyberLean, MovementSettings, Tunneling};
use crate::{
bike::{CyberBikeBody, CyberSteering, CyberWheel, WheelConfig},
bike::{CyberBikeBody, CyberSteering, CyberWheel, WheelConfig, BIKE_WHEEL_COLLISION_GROUP},
input::InputState,
};
pub(super) fn physics_init(
mut config: ResMut<RapierConfiguration>,
settings: Res<MovementSettings>,
) {
config.timestep_mode = TimestepMode::Variable {
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.
pub(super) fn cyber_lean(
bike_state: Query<(&Velocity, &Transform), With<CyberBikeBody>>,
wheels: Query<&Transform, With<CyberWheel>>,
input: Res<InputState>,
gravity_settings: Res<MovementSettings>,
mut lean: ResMut<CyberLean>,
) {
let (velocity, xform) = bike_state.single();
let vel = velocity.linvel.dot(*xform.forward());
let v_squared = vel.powi(2);
let steering_angle = yaw_to_angle(input.yaw);
let wheels: Vec<_> = wheels.iter().map(|w| w.translation).collect();
let wheel_base = (wheels[0] - wheels[1]).length();
let radius = wheel_base / steering_angle.tan();
let gravity = gravity_settings.gravity;
let v2_r = v_squared / radius;
let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3);
if tan_theta.is_finite() && !tan_theta.is_subnormal() {
lean.lean = tan_theta.atan().clamp(-FRAC_PI_4, FRAC_PI_4);
} else {
lean.lean = 0.0;
}
}
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
pub(super) fn falling_cat(
mut bike_query: Query<(
&Transform,
&Velocity,
&mut ExternalForce,
&mut CatControllerState,
)>,
time: Res<Time>,
settings: Res<CatControllerSettings>,
lean: Res<CyberLean>,
mut count: Local<usize>,
) {
let (xform, vel, mut forces, mut control_vars) = bike_query.single_mut();
let world_up = Vec3::Y; //xform.translation.normalize();
let rot = Quat::from_axis_angle(*xform.back(), lean.lean);
let target_up = rotate_point(&world_up, &rot).normalize();
let bike_right = xform.right();
let roll_error = bike_right.dot(target_up);
let pitch_error = world_up.dot(*xform.back());
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
if pitch_error.abs() < 0.95 {
let (derivative, integral) = control_vars.update_roll(roll_error, time.delta_seconds());
let mag =
(settings.kp * roll_error) + (settings.ki * integral) + (settings.kd * derivative);
if mag.is_finite() {
forces.torque += xform.back() * mag;
}
}
}
pub(super) fn wheel_forces(
mut wheels_query: Query<
(
&Transform,
&Velocity,
&mut ExternalForce,
&mut Friction,
Option<&CyberSteering>,
),
With<CyberWheel>,
>,
wheel_config: Res<WheelConfig>,
context: Res<RapierContext>,
settings: Res<MovementSettings>,
input: Res<InputState>,
mut gizmos: Gizmos,
) {
// wheel stuff
let radius = wheel_config.radius;
// inputs
let yaw = input.yaw;
let throttle = input.throttle * settings.accel;
for (xform, velocity, mut external_force, mut friction, steering) in wheels_query.iter_mut() {
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 {
let v = yaw.powi(5) * FRAC_PI_4;
if v.is_normal() {
@ -252,3 +35,211 @@ fn rotate_point(pt: &Vec3, rot: &Quat) -> Vec3 {
// why does this need to be inverted???
-Vec3::from_array([rot_qpt.x, rot_qpt.y, rot_qpt.z])
}
pub(super) fn timestep_setup(mut config: ResMut<RapierConfiguration>) {
let ts = TimestepMode::Fixed {
dt: 1.0 / 60.0,
substeps: 2,
};
config.timestep_mode = ts;
}
/// The gravity vector points from the cyberbike to the center of the planet.
pub(super) fn gravity(
mut query: Query<(&Transform, &mut ExternalForce), With<CyberBikeBody>>,
settings: Res<MovementSettings>,
mut rapier_config: ResMut<RapierConfiguration>,
#[cfg(feature = "inspector")] mut debug_instant: ResMut<ActionDebugInstant>,
) {
let (xform, mut forces) = query.single_mut();
#[cfg(feature = "inspectorb")]
{
if debug_instant.elapsed().as_millis() > 6000 {
dbg!(&forces);
debug_instant.reset();
}
}
rapier_config.gravity = xform.translation.normalize() * -settings.gravity;
forces.force = Vec3::ZERO;
forces.torque = Vec3::ZERO;
}
/// The desired lean angle, given steering input and speed.
pub(super) fn cyber_lean(
bike_state: Query<(&Velocity, &Transform), With<CyberBikeBody>>,
wheels: Query<&Transform, With<CyberWheel>>,
input: Res<InputState>,
gravity_settings: Res<MovementSettings>,
mut lean: ResMut<CyberLean>,
) {
let (velocity, xform) = bike_state.single();
let vel = velocity.linvel.dot(xform.forward());
let v_squared = vel.powi(2);
let steering_angle = yaw_to_angle(input.yaw);
let wheels: Vec<_> = wheels.iter().map(|w| w.translation).collect();
let wheel_base = (wheels[0] - wheels[1]).length();
let radius = wheel_base / steering_angle.tan();
let gravity = gravity_settings.gravity;
let v2_r = v_squared / radius;
let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3);
if tan_theta.is_finite() && !tan_theta.is_subnormal() {
lean.lean = tan_theta.atan().clamp(-FRAC_PI_4, FRAC_PI_4);
} else {
lean.lean = 0.0;
}
}
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
pub(super) fn falling_cat(
mut bike_query: Query<(&Transform, &mut ExternalForce, &mut CatControllerState)>,
time: Res<Time>,
settings: Res<CatControllerSettings>,
lean: Res<CyberLean>,
) {
let (xform, mut forces, mut control_vars) = bike_query.single_mut();
let world_up = xform.translation.normalize();
let rot = Quat::from_axis_angle(xform.back(), lean.lean);
let target_up = rotate_point(&world_up, &rot).normalize();
let bike_right = xform.right();
let roll_error = bike_right.dot(target_up);
let pitch_error = world_up.dot(xform.back());
// only try to correct roll if we're not totally vertical
if pitch_error.abs() < 0.95 {
let (derivative, integral) = control_vars.update_roll(roll_error, time.delta_seconds());
let mag =
(settings.kp * roll_error) + (settings.ki * integral) + (settings.kd * derivative);
if mag.is_finite() {
forces.torque += xform.back() * mag;
}
}
}
/// Apply forces to the cyberbike as a result of input.
pub(super) fn input_forces(
settings: Res<MovementSettings>,
input: Res<InputState>,
mut braking_query: Query<&mut MultibodyJoint, (Without<CyberSteering>, With<CyberWheel>)>,
mut body_query: Query<
(&Transform, &mut ExternalForce),
(With<CyberBikeBody>, Without<CyberSteering>),
>,
mut steering_query: Query<&mut MultibodyJoint, With<CyberSteering>>,
) {
let (xform, mut forces) = body_query.single_mut();
// thrust
let thrust = xform.forward() * input.throttle * settings.accel;
let point = xform.translation + xform.back();
let force = ExternalForce::at_point(thrust, point, xform.translation);
*forces += force;
// brake + thrust
for mut motor in braking_query.iter_mut() {
let factor = if input.brake {
500.00
} else {
input.throttle * settings.accel
};
let speed = if input.brake { 0.0 } else { -70.0 };
motor.data = (*motor
.data
.as_revolute_mut()
.unwrap()
.set_motor_max_force(factor)
.set_motor_velocity(speed, factor))
.into();
}
// steering
let angle = yaw_to_angle(input.yaw);
let mut steering = steering_query.single_mut();
steering.data = (*steering
.data
.as_revolute_mut()
.unwrap()
.set_motor_position(-angle, 100.0, 0.5))
.into();
}
/// Don't let the wheels get stuck underneat the planet
pub(super) fn surface_fix(
mut commands: Commands,
mut wheel_query: Query<
(Entity, &Transform, &mut CollisionGroups),
(With<CyberWheel>, Without<Tunneling>),
>,
span_query: Query<&Transform, With<CyberWheel>>,
config: Res<WheelConfig>,
context: Res<RapierContext>,
) {
let mut wheels = Vec::new();
for xform in span_query.iter() {
wheels.push(xform);
}
let span = (wheels[1].translation - wheels[0].translation).normalize();
for (entity, xform, mut cgroups) in wheel_query.iter_mut() {
//let ray_dir = xform.translation.normalize();
let ray_dir = xform.right().cross(span).normalize();
if let Some(hit) = context.cast_ray_and_get_normal(
xform.translation,
ray_dir,
config.radius * 1.1,
false,
QueryFilter::only_fixed(),
) {
cgroups.memberships = Group::NONE;
cgroups.filters = Group::NONE;
commands.entity(entity).insert(Tunneling {
frames: 3,
dir: hit.1.normal,
});
}
}
}
pub(super) fn tunnel_out(
mut commands: Commands,
mut wheel_query: Query<
(
Entity,
&mut Tunneling,
&mut ExternalForce,
&mut CollisionGroups,
),
With<CyberWheel>,
>,
mprops: Query<&ReadMassProperties, With<CyberBikeBody>>,
settings: Res<MovementSettings>,
) {
let mprops = mprops.single();
for (entity, mut tunneling, mut force, mut cgroups) in wheel_query.iter_mut() {
if tunneling.frames == 0 {
commands.entity(entity).remove::<Tunneling>();
force.force = Vec3::ZERO;
(cgroups.memberships, cgroups.filters) = BIKE_WHEEL_COLLISION_GROUP;
continue;
}
tunneling.frames -= 1;
force.force = tunneling.dir * settings.gravity * 1.5 * mprops.0.mass;
#[cfg(feature = "inspector")]
dbg!(&tunneling);
}
}
/// General velocity-based drag-force calculation; does not take orientation
/// into account.
pub(super) fn drag(mut query: Query<(&Velocity, &mut ExternalForce), With<CyberBikeBody>>) {
let (vels, mut forces) = query.single_mut();
if let Some(vel) = vels.linvel.try_normalize() {
let v2 = vels.linvel.length_squared();
forces.force -= vel * (v2 * 0.02);
}
}

View File

@ -8,10 +8,7 @@ use bevy_rapier3d::prelude::{
};
use super::{spawn_wheels, CyberBikeBody, Meshterial, WheelConfig, BIKE_BODY_COLLISION_GROUP};
use crate::{
action::{CatControllerState, PreviousVelocity},
planet::PLANET_RADIUS,
};
use crate::{action::CatControllerState, planet::PLANET_RADIUS};
pub(super) fn spawn_cyberbike(
mut commands: Commands,
@ -19,12 +16,13 @@ pub(super) fn spawn_cyberbike(
wheel_conf: Res<WheelConfig>,
mut meshterials: Meshterial,
) {
let altitude = 3.0;
let altitude = PLANET_RADIUS - 10.0;
let mut xform = Transform::from_translation(Vec3::Y * altitude);
let mut xform = Transform::from_translation(Vec3::X * altitude)
.with_rotation(Quat::from_axis_angle(Vec3::Z, -89.0f32.to_radians()));
// let right = xform.right() * 350.0;
// xform.translation += right;
let right = xform.right() * 350.0;
xform.translation += right;
let damping = Damping {
angular_damping: 2.0,
@ -66,7 +64,6 @@ pub(super) fn spawn_cyberbike(
Sleeping::disabled(),
Ccd { enabled: true },
ReadMassProperties::default(),
PreviousVelocity::default(),
))
.insert(TransformInterpolation {
start: None,

View File

@ -22,6 +22,7 @@ pub struct WheelConfig {
pub stiffness: f32,
pub damping: f32,
pub radius: f32,
pub thickness: f32,
pub friction: f32,
pub restitution: f32,
pub density: f32,
@ -34,10 +35,11 @@ impl Default for WheelConfig {
rear_back: 1.0,
y: -0.1,
limits: [-0.5, 0.1],
stiffness: 30.0,
stiffness: 190.0,
damping: 8.0,
radius: 0.3,
friction: 0.0,
radius: 0.25,
thickness: 0.11,
friction: 1.2,
restitution: 0.95,
density: 0.05,
}

View File

@ -1,16 +1,17 @@
mod body;
mod components;
//mod controller;
mod wheels;
use bevy::prelude::{App, Assets, Mesh, Plugin, PostStartup, ResMut, StandardMaterial};
use bevy::prelude::{
App, Assets, IntoSystemConfig, Mesh, Plugin, ResMut, StandardMaterial, StartupSet,
};
use bevy_rapier3d::prelude::Group;
pub(crate) use self::components::*;
use self::{body::spawn_cyberbike, wheels::spawn_wheels};
pub const BIKE_BODY_COLLISION_GROUP: (Group, Group) = (Group::GROUP_1, Group::GROUP_1);
pub const BIKE_WHEEL_COLLISION_GROUP: (Group, Group) = (Group::GROUP_2, Group::GROUP_2);
pub const BIKE_WHEEL_COLLISION_GROUP: (Group, Group) = (Group::GROUP_10, Group::GROUP_10);
type Meshterial<'a> = (
ResMut<'a, Assets<Mesh>>,
@ -22,6 +23,6 @@ impl Plugin for CyberBikePlugin {
fn build(&self, app: &mut App) {
app.insert_resource(WheelConfig::default())
.register_type::<WheelConfig>()
.add_systems(PostStartup, spawn_cyberbike);
.add_startup_system(spawn_cyberbike.in_base_set(StartupSet::PostStartup));
}
}

View File

@ -1,35 +1,35 @@
use bevy::prelude::*;
use bevy_rapier3d::{
dynamics::{Ccd, FixedJointBuilder, Velocity},
geometry::{Collider, CollisionGroups, Friction},
prelude::{
ColliderMassProperties, ExternalForce, MultibodyJoint, PrismaticJointBuilder, RigidBody,
Sleeping, TransformInterpolation,
},
use bevy::prelude::{shape::Torus as Tire, *};
use bevy_rapier3d::prelude::{
Ccd, CoefficientCombineRule, Collider, ColliderMassProperties, CollisionGroups, Damping,
ExternalForce, Friction, MultibodyJoint, PrismaticJointBuilder, Restitution,
RevoluteJointBuilder, RigidBody, Sleeping, TransformInterpolation,
};
use super::{CyberSteering, CyberWheel, Meshterial, WheelConfig, BIKE_WHEEL_COLLISION_GROUP};
use crate::action::PreviousVelocity;
pub(crate) fn spawn_wheels(
pub fn spawn_wheels(
commands: &mut Commands,
bike: Entity,
conf: &WheelConfig,
meshterials: &mut Meshterial,
) {
let (membership, filter) = BIKE_WHEEL_COLLISION_GROUP;
let wheels_collision_group = CollisionGroups::new(membership, filter);
let wheel_y = conf.y;
let stiffness = conf.stiffness;
let not_sleeping = Sleeping::disabled();
let ccd = Ccd { enabled: true };
let limits = conf.limits;
let (meshes, materials) = meshterials;
//let rake_vec: Vec3 = Vec3::new(0.0, 1.0, 0.57).normalize(); // about 30
// degrees of rake
let rake_vec: Vec3 = Vec3::new(0.0, 1.0, 0.57).normalize(); // about 30 degrees of rake
let friction = Friction {
coefficient: conf.friction,
combine_rule: CoefficientCombineRule::Average,
};
let mut wheel_poses = Vec::with_capacity(2);
let (membership, filter) = BIKE_WHEEL_COLLISION_GROUP;
let wheels_collision_group = CollisionGroups::new(membership, filter);
// front
{
let wheel_x = 0.0;
@ -47,12 +47,17 @@ pub(crate) fn spawn_wheels(
}
for (offset, steering) in wheel_poses {
let mesh = gen_tires(conf);
let (mesh, collider) = gen_tires(conf);
let material = StandardMaterial {
base_color: Color::YELLOW,
base_color: Color::Rgba {
red: 0.01,
green: 0.01,
blue: 0.01,
alpha: 1.0,
},
alpha_mode: AlphaMode::Opaque,
perceptual_roughness: 0.1,
perceptual_roughness: 0.5,
..Default::default()
};
@ -66,8 +71,7 @@ pub(crate) fn spawn_wheels(
let suspension_damping = conf.damping;
let suspension_axis = if steering.is_some() {
// rake_vec
Vec3::Y
rake_vec
} else {
Vec3::Y
};
@ -83,47 +87,91 @@ pub(crate) fn spawn_wheels(
.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 axel_parent_entity = if let Some(steering) = steering {
let neck_builder =
RevoluteJointBuilder::new(rake_vec).local_anchor1(Vec3::new(0.0, 0.0, 0.1)); // this adds another 0.1m of trail
let neck_joint = MultibodyJoint::new(fork_rb_entity, neck_builder);
let neck = commands.spawn(RigidBody::Dynamic).insert(neck_joint).id();
let neck = commands
.spawn(RigidBody::Dynamic)
.insert(neck_joint)
.insert(steering)
.insert(not_sleeping)
.id();
neck
} else {
fork_rb_entity
};
let axel_builder = FixedJointBuilder::new();
let axel_builder = RevoluteJointBuilder::new(Vec3::X);
let axel_joint = MultibodyJoint::new(axel_parent_entity, axel_builder);
let wheel_damping = Damping {
linear_damping: 0.8,
..Default::default()
};
let c = &mut commands.spawn(pbr_bundle);
c.insert((
commands.spawn(pbr_bundle).insert((
collider,
mass_props,
axel_joint,
CyberWheel,
wheel_damping,
ccd,
not_sleeping,
Velocity::default(),
PreviousVelocity::default(),
axel_joint,
wheels_collision_group,
friction,
CyberWheel,
ExternalForce::default(),
Restitution::new(conf.restitution),
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);
}
}
}
// do mesh shit
fn gen_tires(conf: &WheelConfig) -> Mesh {
fn gen_tires(conf: &WheelConfig) -> (Mesh, Collider) {
let wheel_rad = conf.radius;
let tire = Sphere::new(wheel_rad);
Mesh::from(tire)
let tire_thickness = conf.thickness;
let tire = Tire {
radius: wheel_rad,
ring_radius: tire_thickness,
..Default::default()
};
let mut mesh = Mesh::from(tire);
let tire_verts = mesh
.attribute(Mesh::ATTRIBUTE_POSITION)
.unwrap()
.as_float3()
.unwrap()
.iter()
.map(|v| {
//
let v = Vec3::from_array(*v);
let m = Mat3::from_rotation_z(90.0f32.to_radians());
let p = m.mul_vec3(v);
p.to_array()
})
.collect::<Vec<[f32; 3]>>();
mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, tire_verts);
let mut idxs = Vec::new();
let indices = mesh.indices().unwrap().iter().collect::<Vec<_>>();
for idx in indices.as_slice().chunks_exact(3) {
idxs.push([idx[0] as u32, idx[1] as u32, idx[2] as u32]);
}
let wheel_collider = Collider::convex_decomposition(
&mesh
.attribute(Mesh::ATTRIBUTE_POSITION)
.unwrap()
.as_float3()
.unwrap()
.iter()
.map(|v| Vec3::from_array(*v))
.collect::<Vec<_>>(),
&idxs,
);
(mesh, wheel_collider)
}

View File

@ -67,7 +67,7 @@ fn follow_cyberbike(
offset: Res<DebugCamOffset>,
) {
let bike_xform = *query.p0().single();
let up = Vec3::Y; //bike_xform.translation.normalize();
let up = bike_xform.translation.normalize();
for (mut cam_xform, cam_type) in query.p1().iter_mut() {
match *cam_type {
@ -81,7 +81,7 @@ fn follow_cyberbike(
// handle input pitch
let angle = input.pitch.powi(3) * MAX_PITCH;
let axis = cam_xform.right();
cam_xform.rotate(Quat::from_axis_angle(*axis, angle));
cam_xform.rotate(Quat::from_axis_angle(axis, angle));
}
CyberCameras::Debug => {
let mut ncx = bike_xform.to_owned();
@ -101,7 +101,7 @@ fn update_active_camera(
) {
// find the camera with the current state, set it as the ActiveCamera
query.iter_mut().for_each(|(mut cam, cyber)| {
if cyber.eq(&state.get()) {
if cyber.eq(&state.0) {
cam.is_active = true;
} else {
cam.is_active = false;
@ -112,13 +112,13 @@ fn update_active_camera(
fn cycle_cam_state(
state: Res<State<CyberCameras>>,
mut next: ResMut<NextState<CyberCameras>>,
mut keys: ResMut<ButtonInput<KeyCode>>,
mut keys: ResMut<Input<KeyCode>>,
) {
if keys.just_pressed(KeyCode::KeyD) {
let new_state = state.get().next();
if keys.just_pressed(KeyCode::D) {
let new_state = state.0.next();
info!("{:?}", new_state);
next.set(new_state);
keys.reset(KeyCode::KeyD);
keys.reset(KeyCode::D);
}
}
@ -132,10 +132,9 @@ impl Plugin for CyberCamPlugin {
fn common(app: &mut bevy::prelude::App) {
app.insert_resource(DebugCamOffset::default())
.add_systems(Startup, setup_cybercams)
.init_state::<CyberCameras>()
.add_systems(
Update,
(cycle_cam_state, update_active_camera, follow_cyberbike),
);
.add_startup_system(setup_cybercams)
.add_state::<CyberCameras>()
.add_system(cycle_cam_state)
.add_system(update_active_camera)
.add_system(follow_cyberbike);
}

View File

@ -19,17 +19,16 @@ impl Plugin for CyberGlamorPlugin {
let mode = DebugRenderMode::CONTACTS
| DebugRenderMode::SOLVER_CONTACTS
| DebugRenderMode::JOINTS
| DebugRenderMode::COLLIDER_SHAPES
| DebugRenderMode::RIGID_BODY_AXES;
let rplugin = RapierDebugRenderPlugin {
style,
// always_on_top: true,
always_on_top: true,
enabled: true,
mode,
};
app.add_plugins(rplugin);
app.add_plugin(rplugin);
}
}
}

View File

@ -14,22 +14,22 @@ pub(crate) struct InputState {
pub pitch: f32,
}
fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<ButtonInput<KeyCode>>) {
fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<Input<KeyCode>>) {
let keyset: HashSet<_> = keys.get_pressed().collect();
let shifted = keyset.contains(&KeyCode::ShiftLeft) || keyset.contains(&KeyCode::ShiftRight);
let shifted = keyset.contains(&KeyCode::LShift) || keyset.contains(&KeyCode::RShift);
for key in keyset {
match key {
KeyCode::ArrowLeft => offset.rot -= 5.0,
KeyCode::ArrowRight => offset.rot += 5.0,
KeyCode::ArrowUp => {
KeyCode::Left => offset.rot -= 5.0,
KeyCode::Right => offset.rot += 5.0,
KeyCode::Up => {
if shifted {
offset.alt += 0.5;
} else {
offset.dist -= 0.5;
}
}
KeyCode::ArrowDown => {
KeyCode::Down => {
if shifted {
offset.alt -= 0.5;
} else {
@ -41,17 +41,16 @@ fn update_debug_cam(mut offset: ResMut<DebugCamOffset>, mut keys: ResMut<ButtonI
}
if keys.get_just_released().len() > 0 {
let unpressed =
keys.just_released(KeyCode::ShiftLeft) || keys.just_released(KeyCode::ShiftRight);
let unpressed = keys.just_released(KeyCode::LShift) || keys.just_released(KeyCode::RShift);
keys.reset_all();
if shifted && !unpressed {
keys.press(KeyCode::ShiftLeft);
keys.press(KeyCode::LShift);
}
}
}
fn update_input(mut events: EventReader<GamepadEvent>, mut istate: ResMut<InputState>) {
for pad_event in events.read() {
for pad_event in events.iter() {
match pad_event {
GamepadEvent::Button(button_event) => {
let GamepadButtonChangedEvent {
@ -93,6 +92,7 @@ pub struct CyberInputPlugin;
impl Plugin for CyberInputPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<InputState>()
.add_systems(Update, (update_input, update_debug_cam));
.add_system(update_input)
.add_system(update_debug_cam);
}
}

View File

@ -2,7 +2,7 @@ use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};
use crate::planet::PLANET_RADIUS;
pub const LIGHT_RANGE: f32 = 900.0;
pub const LIGHT_RANGE: f32 = 90.0;
fn spawn_static_lights(
mut commands: Commands,
@ -10,37 +10,88 @@ fn spawn_static_lights(
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let pink_light = PointLight {
intensity: 10_00.0,
intensity: 1_00.0,
range: LIGHT_RANGE,
color: Color::WHITE,
radius: 10.0,
color: Color::PINK,
radius: 1.0,
shadows_enabled: true,
..Default::default()
};
let blue_light = PointLight {
intensity: 1_000.0,
range: LIGHT_RANGE,
color: Color::BLUE,
radius: 1.0,
shadows_enabled: true,
..Default::default()
};
commands.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 100.0,
brightness: 0.2,
});
// let _cascade_shadow_config = CascadeShadowConfigBuilder {
// first_cascade_far_bound: 0.3,
// maximum_distance: 3.0,
// ..default()
// }
// .build();
let _cascade_shadow_config = CascadeShadowConfigBuilder {
first_cascade_far_bound: 0.3,
maximum_distance: 3.0,
..default()
}
.build();
// up light
commands.spawn(PointLightBundle {
transform: Transform::from_xyz(0.0, 100.0, 0.0),
point_light: pink_light,
..Default::default()
});
commands
.spawn(PointLightBundle {
transform: Transform::from_xyz(0.0, PLANET_RADIUS + 30.0, 0.0),
point_light: pink_light,
..Default::default()
})
.with_children(|builder| {
builder.spawn(PbrBundle {
mesh: meshes.add(
Mesh::try_from(shape::Icosphere {
radius: 10.0,
subdivisions: 2,
})
.unwrap(),
),
material: materials.add(StandardMaterial {
base_color: Color::BLUE,
emissive: Color::PINK,
..Default::default()
}),
..Default::default()
});
});
// down light
commands
.spawn(PointLightBundle {
transform: Transform::from_xyz(0.0, -PLANET_RADIUS - 30.0, 0.0),
point_light: blue_light,
..Default::default()
})
.with_children(|builder| {
builder.spawn(PbrBundle {
mesh: meshes.add(
Mesh::try_from(shape::Icosphere {
radius: 10.0,
subdivisions: 2,
})
.unwrap(),
),
material: materials.add(StandardMaterial {
base_color: Color::PINK,
emissive: Color::BLUE,
..Default::default()
}),
..Default::default()
});
});
}
pub struct CyberSpaceLightsPlugin;
impl Plugin for CyberSpaceLightsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, spawn_static_lights);
app.add_startup_system(spawn_static_lights);
}
}

View File

@ -21,20 +21,18 @@ fn main() {
}),
..Default::default()
}))
.add_plugins((
CyberPlanetPlugin,
CyberInputPlugin,
CyberActionPlugin,
CyberCamPlugin,
CyberSpaceLightsPlugin,
CyberUIPlugin,
CyberBikePlugin,
))
.add_systems(Startup, disable_mouse_trap)
.add_systems(Update, bevy::window::close_on_esc);
.add_plugin(CyberPlanetPlugin)
.add_plugin(CyberInputPlugin)
.add_plugin(CyberActionPlugin)
.add_plugin(CyberCamPlugin)
.add_plugin(CyberSpaceLightsPlugin)
.add_plugin(CyberUIPlugin)
.add_plugin(CyberBikePlugin)
.add_startup_system(disable_mouse_trap)
.add_system(bevy::window::close_on_esc);
#[cfg(feature = "inspector")]
app.add_plugins(CyberGlamorPlugin);
app.add_plugin(CyberGlamorPlugin);
app.run();
}

View File

@ -1,8 +1,14 @@
use bevy::{prelude::*, render::color::Color};
use bevy::{
prelude::{shape::Icosphere, *},
render::{color::Color, mesh::Indices},
};
use bevy_rapier3d::prelude::*;
// use noise::{HybridMulti, NoiseFn, SuperSimplex};
use hexasphere::shapes::IcoSphere;
use noise::{HybridMulti, NoiseFn, SuperSimplex};
use rand::{Rng, SeedableRng};
use wgpu::PrimitiveTopology;
pub const PLANET_RADIUS: f32 = 2_000.0;
pub const PLANET_RADIUS: f32 = 4_000.0;
pub const PLANET_HUE: f32 = 31.0;
pub const PLANET_SATURATION: f32 = 1.0;
@ -15,10 +21,14 @@ fn spawn_planet(
mut materials: ResMut<Assets<StandardMaterial>>,
) {
//let color = Color::rgb(0.74, 0.5334, 0.176);
let isphere = Icosphere {
radius: PLANET_RADIUS,
subdivisions: 88,
};
let (mesh, shape) = gen_planet(2999.9);
let (mesh, shape) = gen_planet(isphere);
let pbody = (RigidBody::Fixed, Transform::from_xyz(0.0, 0.1, 0.0));
let pbody = (RigidBody::Fixed, Ccd { enabled: true });
let pcollide = (
shape,
@ -27,19 +37,12 @@ fn spawn_planet(
..Default::default()
},
Restitution::new(0.8),
Transform::from_xyz(0.0, 1.0, 0.0),
CollisionGroups::default(), // all colliders
Ccd { enabled: true },
);
commands
.spawn(PbrBundle {
mesh: meshes.add(mesh),
material: materials.add(StandardMaterial {
base_color: Color::GREEN,
..Default::default()
}),
transform: Transform::from_xyz(0.0, 0.1, 0.0),
material: materials.add(Color::WHITE.into()),
..Default::default()
})
.insert(pbody)
@ -50,7 +53,7 @@ fn spawn_planet(
pub struct CyberPlanetPlugin;
impl Plugin for CyberPlanetPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, spawn_planet);
app.add_startup_system(spawn_planet);
}
}
@ -58,10 +61,82 @@ impl Plugin for CyberPlanetPlugin {
// utils
//---------------------------------------------------------------------
fn gen_planet(span: f32) -> (Mesh, Collider) {
let mut mesh = Cuboid::new(span, 1.0, span);
fn gen_planet(sphere: Icosphere) -> (Mesh, Collider) {
// straight-up stolen from Bevy's impl of Mesh from Icosphere, so I can do the
// displacement before normals are calculated.
let generated = IcoSphere::new(sphere.subdivisions, |point| {
let inclination = point.y.acos();
let azimuth = point.z.atan2(point.x);
let collider = Collider::cuboid(span / 2.0, 0.5, span / 2.0);
let norm_inclination = inclination / std::f32::consts::PI;
let norm_azimuth = 0.5 - (azimuth / std::f32::consts::TAU);
(mesh.mesh(), collider)
[norm_azimuth, norm_inclination]
});
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) * sphere.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| Vect::from_slice(p)).collect(), idxs);
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(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).as_linear_rgba_f32();
for _ in 0..3 {
colors.push(color);
}
}
dbg!(&colors.len());
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
(mesh, collider)
}

View File

@ -1,10 +1,9 @@
use bevy::{
app::{Startup, Update},
prelude::{
AlignSelf, App, AssetServer, Color, Commands, Component, Plugin, Query, Res, Style, Text,
TextBundle, TextSection, TextStyle, Transform, With,
},
use bevy::prelude::{
AlignSelf, App, AssetServer, Color, Commands, Component, Plugin, Query, Res, Style, Text,
TextBundle, TextSection, TextStyle, Transform, With,
};
#[cfg(feature = "inspector")]
use bevy_inspector_egui::quick::WorldInspectorPlugin;
use bevy_rapier3d::prelude::Velocity;
use crate::bike::CyberBikeBody;
@ -43,7 +42,7 @@ fn update_ui(
) {
let mut text = text_query.single_mut();
let (velocity, xform) = state_query.single();
let speed = velocity.linvel.dot(*xform.forward());
let speed = velocity.linvel.dot(xform.forward());
text.sections[0].value = format!("spd: {:.2}", speed);
}
@ -51,8 +50,9 @@ pub struct CyberUIPlugin;
impl Plugin for CyberUIPlugin {
fn build(&self, app: &mut App) {
//
app.add_systems(Startup, setup_ui)
.add_systems(Update, update_ui);
#[cfg(feature = "inspector")]
app.add_plugin(WorldInspectorPlugin);
app.add_startup_system(setup_ui).add_system(update_ui);
}
}