Compare commits
3 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aae922d39b | ||
|
|
394bc66207 | ||
|
|
2d9247448b |
3 changed files with 350 additions and 329 deletions
337
src/lib.rs
337
src/lib.rs
|
|
@ -1,30 +1,22 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use argh::FromArgs;
|
||||
use async_channel::{bounded, Receiver, Sender};
|
||||
use bevy::{input::keyboard::Key, prelude::*, tasks::AsyncComputeTaskPool};
|
||||
use rand::RngExt;
|
||||
use async_channel::{Receiver, Sender};
|
||||
use bevy::prelude::{Component, Deref, DerefMut, Entity, Resource, Vec, Vec3};
|
||||
use rstar::{primitives::GeomWithData, RTree};
|
||||
|
||||
mod systems;
|
||||
|
||||
pub use systems::{
|
||||
init_naybs, rotate_camera, setup_channels, spawn_background, turkey_time, update_buddies,
|
||||
update_gizmos, update_naybs, update_pos, update_vel,
|
||||
};
|
||||
|
||||
type IndexElement = GeomWithData<[f32; 3], Entity>;
|
||||
type Index = RTree<IndexElement>;
|
||||
|
||||
type EntityPos = (Vec3, Entity);
|
||||
|
||||
// toid stuff
|
||||
const SPEED: f32 = 2.0;
|
||||
const SPEED_DIFF_RANGE: f32 = 0.1; // +/- 10%
|
||||
const MAX_DELTA_V: f32 = std::f32::consts::PI * 2.0; // basically 360
|
||||
// degrees/sec
|
||||
|
||||
// how far from origin before really wanting to come back
|
||||
const RADIUS: f32 = 40.0;
|
||||
|
||||
// how close to try to stay to your buddies
|
||||
const BUDDY_RADIUS: f32 = 10.0;
|
||||
|
||||
const MIN_ALTITUDE: f32 = 3.5;
|
||||
|
||||
#[derive(Debug, FromArgs, Resource)]
|
||||
/// Toid Watching
|
||||
pub struct Config {
|
||||
|
|
@ -78,314 +70,3 @@ pub struct Toid {
|
|||
|
||||
#[derive(Debug, Default, Clone, Copy, Deref, DerefMut, Resource)]
|
||||
pub struct LookAt(Vec3);
|
||||
|
||||
pub fn setup_channels(mut commands: Commands) {
|
||||
let (raw_tx, raw_rx) = bounded(1);
|
||||
commands.insert_resource(RawPosChannel {
|
||||
tx: raw_tx,
|
||||
rx: Some(raw_rx),
|
||||
});
|
||||
|
||||
let (idx_tx, idx_rx) = bounded(1);
|
||||
commands.insert_resource(IndexChannel {
|
||||
tx: idx_tx,
|
||||
rx: idx_rx,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_naybs(
|
||||
mut naybs: ResMut<Naybs>,
|
||||
raw_chan: Res<RawPosChannel>,
|
||||
positions: Res<Positions>,
|
||||
idx_chan: Res<IndexChannel>,
|
||||
) {
|
||||
if let Ok(idx_msg) = idx_chan.rx.try_recv() {
|
||||
naybs.index = idx_msg.index;
|
||||
let _ = naybs.positions.insert(idx_msg.raw);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(mut raw) = naybs.positions.take() {
|
||||
for (i, (entity, pos)) in positions.iter().enumerate() {
|
||||
raw[i] = (*pos, *entity);
|
||||
}
|
||||
|
||||
if let Err(e) = raw_chan.tx.try_send(RawMessage(raw)) {
|
||||
let RawMessage(raw) = e.into_inner();
|
||||
let _ = naybs.positions.insert(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_naybs(positions: Res<Positions>, mut naybs: ResMut<Naybs>) {
|
||||
let mut raw = Vec::with_capacity(positions.len());
|
||||
for (entity, pos) in positions.iter() {
|
||||
raw.push((*pos, *entity));
|
||||
}
|
||||
naybs.positions = Some(raw);
|
||||
}
|
||||
|
||||
pub fn spawn_background(
|
||||
mut raw_chan: ResMut<RawPosChannel>,
|
||||
idx_chan: Res<IndexChannel>,
|
||||
mut handle: ResMut<BackgroundHandle>,
|
||||
) {
|
||||
let pool = AsyncComputeTaskPool::get();
|
||||
|
||||
let raw_rx = raw_chan.rx.take().unwrap();
|
||||
let idx_tx = idx_chan.tx.clone();
|
||||
|
||||
let task = pool.spawn(async move {
|
||||
let tx = idx_tx;
|
||||
let rx = raw_rx;
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(RawMessage(e_posns)) => {
|
||||
let idx = RTree::bulk_load(
|
||||
e_posns
|
||||
.iter()
|
||||
.map(|(p, e)| GeomWithData::new(p.to_array(), *e))
|
||||
.collect(),
|
||||
);
|
||||
let msg = IndexMessage {
|
||||
raw: e_posns,
|
||||
index: idx,
|
||||
};
|
||||
if tx.send(msg).await.is_err() {
|
||||
break;
|
||||
};
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _ = handle.0.insert(task);
|
||||
}
|
||||
|
||||
pub fn turkey_time(
|
||||
commands: &mut Commands,
|
||||
r: &mut impl rand::prelude::Rng,
|
||||
assets: &AssetServer,
|
||||
positions: &mut HashMap<Entity, Vec3>,
|
||||
) {
|
||||
let speed_diff = r.random_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE);
|
||||
let speed = SPEED + (SPEED * speed_diff);
|
||||
let vel = unit_vec(r) * speed;
|
||||
let buddies = r.random_range(6..=8);
|
||||
let x = r.random_range(-10.0..=10.0);
|
||||
let z = r.random_range(-10.0..=10.0);
|
||||
let y = r.random_range(MIN_ALTITUDE..=(MIN_ALTITUDE + 5.5));
|
||||
|
||||
let entity = commands
|
||||
.spawn((
|
||||
Velocity(vel),
|
||||
Buddies::default(),
|
||||
Toid { speed, buddies },
|
||||
Transform::from_xyz(x, y, z),
|
||||
Visibility::Visible,
|
||||
))
|
||||
.with_children(|t| {
|
||||
t.spawn(WorldAssetRoot(
|
||||
assets.load(GltfAssetLabel::Scene(0).from_asset("models/boid.glb")),
|
||||
))
|
||||
.insert(Transform::default())
|
||||
//.insert(Visibility::Visible);
|
||||
.insert(Transform::from_rotation(Quat::from_axis_angle(
|
||||
Vec3::Y,
|
||||
-std::f32::consts::FRAC_PI_2,
|
||||
)));
|
||||
})
|
||||
.id();
|
||||
|
||||
positions.insert(entity, Vec3::new(x, y, z));
|
||||
}
|
||||
|
||||
pub fn update_vel(
|
||||
mut toids: Query<(&Transform, &mut Velocity, &Toid, &Buddies, Entity)>,
|
||||
time: Res<Time>,
|
||||
positions: Res<Positions>,
|
||||
index: Res<Naybs>,
|
||||
) {
|
||||
let dt = time.delta_secs();
|
||||
let max_delta = MAX_DELTA_V * dt;
|
||||
for (xform, mut vel, toid, buddies, entity) in toids.iter_mut() {
|
||||
let speed = toid.speed;
|
||||
let mut dir = vel.normalize();
|
||||
let pos = xform.translation;
|
||||
let original_dir = dir;
|
||||
|
||||
// find buddies and orient; point more towards further-away buddies
|
||||
for buddy in buddies.0.iter() {
|
||||
let bp = positions.get(buddy).unwrap();
|
||||
let bdir = *bp - pos;
|
||||
let dist = bdir.length();
|
||||
let rot = Quat::from_rotation_arc(dir, bdir.normalize());
|
||||
let s = (dist / (BUDDY_RADIUS * 1.2)).min(1.0);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// avoid flying into neighbors
|
||||
let min_dist = speed;
|
||||
for neighbor in index
|
||||
.index
|
||||
.locate_within_distance(pos.to_array(), min_dist)
|
||||
.filter(|n| n.data != entity)
|
||||
{
|
||||
let bp = Vec3::from_array(*neighbor.geom());
|
||||
let bdir = pos - bp;
|
||||
let dist = bdir.length();
|
||||
let s = 1.0 - (dist / min_dist).min(1.0);
|
||||
let rot = Quat::from_rotation_arc(dir, bdir.normalize());
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// nudge toward origin if too far
|
||||
{
|
||||
let dist = pos.length();
|
||||
let toward_origin = -pos.normalize();
|
||||
let s = (dist / RADIUS).min(1.0);
|
||||
let rot = Quat::from_rotation_arc(dir, toward_origin);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// nudge up if too low
|
||||
if pos.y < MIN_ALTITUDE {
|
||||
let dh = MIN_ALTITUDE - pos.y;
|
||||
let s = (dh / MIN_ALTITUDE).min(1.0);
|
||||
let rot = Quat::from_rotation_arc(dir, Vec3::Y);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// make sure velocity doesn't change too suddenly
|
||||
let delta = dir.dot(original_dir).acos();
|
||||
if delta > max_delta {
|
||||
let s = max_delta / delta;
|
||||
let rot = Quat::from_rotation_arc(original_dir, dir);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(original_dir).normalize();
|
||||
}
|
||||
|
||||
**vel = dir * speed;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_pos(
|
||||
mut toids: Query<(&mut Transform, &Velocity, Entity), With<Toid>>,
|
||||
mut positions: ResMut<Positions>,
|
||||
mut lookat: ResMut<LookAt>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let mut new_look = Vec3::ZERO;
|
||||
let dt = time.delta_secs();
|
||||
for (mut xform, vel, entity) in toids.iter_mut() {
|
||||
let look_at = xform.translation + vel.0;
|
||||
xform.translation += vel.0 * dt;
|
||||
xform.look_at(look_at, Vec3::Y);
|
||||
*positions.entry(entity).or_insert(Vec3::ZERO) = xform.translation;
|
||||
new_look += xform.translation;
|
||||
}
|
||||
**lookat = new_look / positions.len() as f32;
|
||||
}
|
||||
|
||||
pub fn update_buddies(
|
||||
mut toids: Query<(&Transform, Entity, &Toid, &mut Buddies)>,
|
||||
index: Res<Naybs>,
|
||||
positions: Res<Positions>,
|
||||
) {
|
||||
let d2 = (BUDDY_RADIUS * 1.5).powi(2);
|
||||
for (xform, entity, toid, mut buddies) in toids.iter_mut() {
|
||||
let pos = xform.translation;
|
||||
for buddy in buddies.clone().iter() {
|
||||
let bp = positions[buddy];
|
||||
let bd2 = (bp - pos).length_squared();
|
||||
if bd2 > d2 {
|
||||
buddies.remove(buddy);
|
||||
}
|
||||
}
|
||||
|
||||
if buddies.len() < toid.buddies {
|
||||
let diff = toid.buddies - buddies.len();
|
||||
for neighbor in index
|
||||
.index
|
||||
.nearest_neighbor_iter(pos.to_array())
|
||||
.filter(|n| n.data != entity)
|
||||
.take(diff)
|
||||
{
|
||||
buddies.insert(neighbor.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_gizmos(toids: Query<&Transform, With<Toid>>, mut gizmos: Gizmos) {
|
||||
for toid in toids.iter() {
|
||||
let pos = toid.translation;
|
||||
let up = toid.up().as_vec3();
|
||||
let forward = toid.forward().as_vec3();
|
||||
let right = toid.right().as_vec3();
|
||||
gizmos.ray(pos, pos + forward, Color::srgb(1., 0., 0.));
|
||||
gizmos.ray(pos, pos + up, Color::srgb(0., 1., 0.));
|
||||
gizmos.ray(pos, pos + right, Color::srgb(0., 0., 1.));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate_camera(
|
||||
mut query: Query<&mut Transform, With<Camera>>,
|
||||
keys: Res<ButtonInput<Key>>,
|
||||
lookat: Res<LookAt>,
|
||||
) {
|
||||
let mut xform = query.single_mut().unwrap();
|
||||
|
||||
let forward = xform.forward().as_vec3() * 0.7;
|
||||
let right = xform.right().as_vec3();
|
||||
|
||||
if keys.pressed(Key::ArrowRight) {
|
||||
xform.translation += right;
|
||||
}
|
||||
if keys.pressed(Key::ArrowLeft) {
|
||||
xform.translation -= right;
|
||||
}
|
||||
|
||||
if keys.pressed(Key::ArrowUp) {
|
||||
if keys.pressed(Key::Shift) {
|
||||
xform.translation += Vec3::Y;
|
||||
} else {
|
||||
xform.translation += forward;
|
||||
}
|
||||
}
|
||||
|
||||
if keys.pressed(Key::ArrowDown) {
|
||||
if keys.pressed(Key::Shift) {
|
||||
xform.translation -= Vec3::Y;
|
||||
} else {
|
||||
xform.translation -= forward;
|
||||
}
|
||||
}
|
||||
|
||||
xform.look_at(**lookat, Vec3::Y);
|
||||
}
|
||||
|
||||
//-************************************************************************
|
||||
// util
|
||||
//-************************************************************************
|
||||
|
||||
pub fn unit_vec(r: &mut impl rand::Rng) -> Vec3 {
|
||||
let mut x1: f32 = 0.0;
|
||||
let mut x2: f32 = 0.0;
|
||||
let mut ssum = f32::MAX;
|
||||
while ssum >= 1.0 {
|
||||
x1 = r.random_range(-1.0..=1.0);
|
||||
x2 = r.random_range(-1.0..=1.0);
|
||||
ssum = x1.powi(2) + x2.powi(2);
|
||||
}
|
||||
let sqrt = (1.0 - ssum).sqrt();
|
||||
let x = 2.0 * x1 * sqrt;
|
||||
let y = 2.0 * x2 * sqrt;
|
||||
let z = 1.0 - 2.0 * ssum;
|
||||
Vec3::new(x, y, z).normalize()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
input::keyboard::Key,
|
||||
prelude::*,
|
||||
prelude::{
|
||||
children, default, px, App, AssetServer, Assets, ButtonInput, Camera3d, ClearColor, Color,
|
||||
Commands, DefaultPlugins, Entity, FontSize, GlobalAmbientLight, IntoScheduleConfigs, Mesh,
|
||||
Mesh3d, MeshMaterial3d, Node, Plane3d, PositionType, Query, Res, ResMut, StandardMaterial,
|
||||
Startup, Text, TextFont, Transform, Update, Vec2, Vec3, Visibility, Window,
|
||||
},
|
||||
};
|
||||
use rstar::RTree;
|
||||
use toids::{
|
||||
|
|
|
|||
335
src/systems.rs
Normal file
335
src/systems.rs
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use async_channel::bounded;
|
||||
use bevy::{input::keyboard::Key, prelude::*, tasks::AsyncComputeTaskPool};
|
||||
use rand::RngExt;
|
||||
|
||||
use super::{
|
||||
BackgroundHandle, Buddies, GeomWithData, IndexChannel, IndexMessage, LookAt, Naybs, Positions,
|
||||
RTree, RawMessage, RawPosChannel, Toid, Velocity,
|
||||
};
|
||||
|
||||
// toid stuff
|
||||
const SPEED: f32 = 6.0;
|
||||
const SPEED_DIFF_RANGE: f32 = 0.2; // +/- 10%
|
||||
const MAX_DELTA_V: f32 = std::f32::consts::PI * 1.5; // basically 360
|
||||
// degrees/sec
|
||||
|
||||
// how far from origin before really wanting to come back
|
||||
const RADIUS: f32 = 40.0;
|
||||
|
||||
// how close to try to stay to your buddies
|
||||
const BUDDY_RADIUS: f32 = 10.0;
|
||||
|
||||
const MIN_ALTITUDE: f32 = 3.5;
|
||||
|
||||
pub fn setup_channels(mut commands: Commands) {
|
||||
let (raw_tx, raw_rx) = bounded(1);
|
||||
commands.insert_resource(RawPosChannel {
|
||||
tx: raw_tx,
|
||||
rx: Some(raw_rx),
|
||||
});
|
||||
|
||||
let (idx_tx, idx_rx) = bounded(1);
|
||||
commands.insert_resource(IndexChannel {
|
||||
tx: idx_tx,
|
||||
rx: idx_rx,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_naybs(
|
||||
mut naybs: ResMut<Naybs>,
|
||||
raw_chan: Res<RawPosChannel>,
|
||||
positions: Res<Positions>,
|
||||
idx_chan: Res<IndexChannel>,
|
||||
) {
|
||||
if let Ok(idx_msg) = idx_chan.rx.try_recv() {
|
||||
naybs.index = idx_msg.index;
|
||||
let _ = naybs.positions.insert(idx_msg.raw);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(mut raw) = naybs.positions.take() {
|
||||
for (i, (entity, pos)) in positions.iter().enumerate() {
|
||||
raw[i] = (*pos, *entity);
|
||||
}
|
||||
|
||||
if let Err(e) = raw_chan.tx.try_send(RawMessage(raw)) {
|
||||
let RawMessage(raw) = e.into_inner();
|
||||
let _ = naybs.positions.insert(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_naybs(positions: Res<Positions>, mut naybs: ResMut<Naybs>) {
|
||||
let mut raw = Vec::with_capacity(positions.len());
|
||||
for (entity, pos) in positions.iter() {
|
||||
raw.push((*pos, *entity));
|
||||
}
|
||||
naybs.positions = Some(raw);
|
||||
}
|
||||
|
||||
pub fn spawn_background(
|
||||
mut raw_chan: ResMut<RawPosChannel>,
|
||||
idx_chan: Res<IndexChannel>,
|
||||
mut handle: ResMut<BackgroundHandle>,
|
||||
) {
|
||||
let pool = AsyncComputeTaskPool::get();
|
||||
|
||||
let raw_rx = raw_chan.rx.take().unwrap();
|
||||
let idx_tx = idx_chan.tx.clone();
|
||||
|
||||
let task = pool.spawn(async move {
|
||||
let tx = idx_tx;
|
||||
let rx = raw_rx;
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(RawMessage(e_posns)) => {
|
||||
let idx = RTree::bulk_load(
|
||||
e_posns
|
||||
.iter()
|
||||
.map(|(p, e)| GeomWithData::new(p.to_array(), *e))
|
||||
.collect(),
|
||||
);
|
||||
let msg = IndexMessage {
|
||||
raw: e_posns,
|
||||
index: idx,
|
||||
};
|
||||
if tx.send(msg).await.is_err() {
|
||||
break;
|
||||
};
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let _ = handle.0.insert(task);
|
||||
}
|
||||
|
||||
pub fn turkey_time(
|
||||
commands: &mut Commands,
|
||||
r: &mut impl rand::prelude::Rng,
|
||||
assets: &AssetServer,
|
||||
positions: &mut HashMap<Entity, Vec3>,
|
||||
) {
|
||||
let speed_diff = r.random_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE);
|
||||
let speed = SPEED + (SPEED * speed_diff);
|
||||
let vel = unit_vec(r) * speed;
|
||||
let buddies = r.random_range(6..=8);
|
||||
let x = r.random_range(-10.0..=10.0);
|
||||
let z = r.random_range(-10.0..=10.0);
|
||||
let y = r.random_range(MIN_ALTITUDE..=(MIN_ALTITUDE + 5.5));
|
||||
|
||||
let entity = commands
|
||||
.spawn((
|
||||
Velocity(vel),
|
||||
Buddies::default(),
|
||||
Toid { speed, buddies },
|
||||
Transform::from_xyz(x, y, z),
|
||||
Visibility::Visible,
|
||||
))
|
||||
.with_children(|t| {
|
||||
t.spawn(WorldAssetRoot(
|
||||
assets.load(GltfAssetLabel::Scene(0).from_asset("models/boid.glb")),
|
||||
))
|
||||
.insert(Transform::default())
|
||||
//.insert(Visibility::Visible);
|
||||
.insert(Transform::from_rotation(Quat::from_axis_angle(
|
||||
Vec3::Y,
|
||||
-std::f32::consts::FRAC_PI_2,
|
||||
)));
|
||||
})
|
||||
.id();
|
||||
|
||||
positions.insert(entity, Vec3::new(x, y, z));
|
||||
}
|
||||
|
||||
pub fn update_vel(
|
||||
mut toids: Query<(&Transform, &mut Velocity, &Toid, &Buddies, Entity)>,
|
||||
time: Res<Time>,
|
||||
positions: Res<Positions>,
|
||||
index: Res<Naybs>,
|
||||
) {
|
||||
let dt = time.delta_secs();
|
||||
let max_delta = MAX_DELTA_V * dt;
|
||||
for (xform, mut vel, toid, buddies, entity) in toids.iter_mut() {
|
||||
let speed = toid.speed;
|
||||
let mut dir = vel.normalize();
|
||||
let pos = xform.translation;
|
||||
let original_dir = dir;
|
||||
|
||||
// find buddies and orient; point more towards further-away buddies
|
||||
for buddy in buddies.0.iter() {
|
||||
let bp = positions.get(buddy).unwrap();
|
||||
let bdir = *bp - pos;
|
||||
let dist = bdir.length();
|
||||
let rot = Quat::from_rotation_arc(dir, bdir.normalize());
|
||||
let s = (dist / (BUDDY_RADIUS * 1.2)).min(1.0);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// avoid flying into neighbors
|
||||
let min_dist = speed;
|
||||
for neighbor in index
|
||||
.index
|
||||
.locate_within_distance(pos.to_array(), min_dist)
|
||||
.filter(|n| n.data != entity)
|
||||
{
|
||||
let bp = Vec3::from_array(*neighbor.geom());
|
||||
let bdir = pos - bp;
|
||||
let dist = bdir.length();
|
||||
let s = 1.0 - (dist / min_dist).min(1.0);
|
||||
let rot = Quat::from_rotation_arc(dir, bdir.normalize());
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// nudge toward origin if too far
|
||||
{
|
||||
let dist = pos.length();
|
||||
let toward_origin = -pos.normalize();
|
||||
let s = (dist / RADIUS).min(1.0);
|
||||
let rot = Quat::from_rotation_arc(dir, toward_origin);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// nudge up if too low
|
||||
if pos.y < MIN_ALTITUDE {
|
||||
let dh = MIN_ALTITUDE - pos.y;
|
||||
let s = (dh / MIN_ALTITUDE).min(1.0);
|
||||
let rot = Quat::from_rotation_arc(dir, Vec3::Y);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(dir).normalize();
|
||||
}
|
||||
|
||||
// make sure velocity doesn't change too suddenly
|
||||
let delta = dir.dot(original_dir).acos();
|
||||
if delta > max_delta {
|
||||
let s = max_delta / delta;
|
||||
let rot = Quat::from_rotation_arc(original_dir, dir);
|
||||
let rot = Quat::IDENTITY.slerp(rot, s);
|
||||
dir = rot.mul_vec3(original_dir).normalize();
|
||||
}
|
||||
|
||||
**vel = dir * speed;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_pos(
|
||||
mut toids: Query<(&mut Transform, &Velocity, Entity), With<Toid>>,
|
||||
mut positions: ResMut<Positions>,
|
||||
mut lookat: ResMut<LookAt>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let mut new_look = Vec3::ZERO;
|
||||
let dt = time.delta_secs();
|
||||
for (mut xform, vel, entity) in toids.iter_mut() {
|
||||
let look_at = xform.translation + vel.0;
|
||||
xform.translation += vel.0 * dt;
|
||||
xform.look_at(look_at, Vec3::Y);
|
||||
*positions.entry(entity).or_insert(Vec3::ZERO) = xform.translation;
|
||||
new_look += xform.translation;
|
||||
}
|
||||
**lookat = new_look / positions.len() as f32;
|
||||
}
|
||||
|
||||
pub fn update_buddies(
|
||||
mut toids: Query<(&Transform, Entity, &Toid, &mut Buddies)>,
|
||||
index: Res<Naybs>,
|
||||
positions: Res<Positions>,
|
||||
) {
|
||||
let d2 = (BUDDY_RADIUS * 1.5).powi(2);
|
||||
for (xform, entity, toid, mut buddies) in toids.iter_mut() {
|
||||
let pos = xform.translation;
|
||||
for buddy in buddies.clone().iter() {
|
||||
let bp = positions[buddy];
|
||||
let bd2 = (bp - pos).length_squared();
|
||||
if bd2 > d2 {
|
||||
buddies.remove(buddy);
|
||||
}
|
||||
}
|
||||
|
||||
if buddies.len() < toid.buddies {
|
||||
let diff = toid.buddies - buddies.len();
|
||||
for neighbor in index
|
||||
.index
|
||||
.nearest_neighbor_iter(pos.to_array())
|
||||
.filter(|n| n.data != entity)
|
||||
.take(diff)
|
||||
{
|
||||
buddies.insert(neighbor.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_gizmos(toids: Query<&Transform, With<Toid>>, mut gizmos: Gizmos) {
|
||||
for toid in toids.iter() {
|
||||
let pos = toid.translation;
|
||||
let up = toid.up().as_vec3();
|
||||
let forward = toid.forward().as_vec3();
|
||||
let right = toid.right().as_vec3();
|
||||
gizmos.ray(pos, pos + forward, Color::srgb(1., 0., 0.));
|
||||
gizmos.ray(pos, pos + up, Color::srgb(0., 1., 0.));
|
||||
gizmos.ray(pos, pos + right, Color::srgb(0., 0., 1.));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rotate_camera(
|
||||
mut query: Query<&mut Transform, With<Camera>>,
|
||||
keys: Res<ButtonInput<Key>>,
|
||||
lookat: Res<LookAt>,
|
||||
) {
|
||||
let mut xform = query.single_mut().unwrap();
|
||||
|
||||
let forward = xform.forward().as_vec3() * 0.7;
|
||||
let right = xform.right().as_vec3();
|
||||
|
||||
if keys.pressed(Key::ArrowRight) {
|
||||
xform.translation += right;
|
||||
}
|
||||
if keys.pressed(Key::ArrowLeft) {
|
||||
xform.translation -= right;
|
||||
}
|
||||
|
||||
if keys.pressed(Key::ArrowUp) {
|
||||
if keys.pressed(Key::Shift) {
|
||||
xform.translation += Vec3::Y;
|
||||
} else {
|
||||
xform.translation += forward;
|
||||
}
|
||||
}
|
||||
|
||||
if keys.pressed(Key::ArrowDown) {
|
||||
if keys.pressed(Key::Shift) {
|
||||
xform.translation -= Vec3::Y;
|
||||
} else {
|
||||
xform.translation -= forward;
|
||||
}
|
||||
}
|
||||
|
||||
xform.look_at(**lookat, Vec3::Y);
|
||||
}
|
||||
|
||||
//-************************************************************************
|
||||
// util
|
||||
//-************************************************************************
|
||||
|
||||
pub fn unit_vec(r: &mut impl rand::Rng) -> Vec3 {
|
||||
let mut x1: f32 = 0.0;
|
||||
let mut x2: f32 = 0.0;
|
||||
let mut ssum = f32::MAX;
|
||||
while ssum >= 1.0 {
|
||||
x1 = r.random_range(-1.0..=1.0);
|
||||
x2 = r.random_range(-1.0..=1.0);
|
||||
ssum = x1.powi(2) + x2.powi(2);
|
||||
}
|
||||
let sqrt = (1.0 - ssum).sqrt();
|
||||
let x = 2.0 * x1 * sqrt;
|
||||
let y = 2.0 * x2 * sqrt;
|
||||
let z = 1.0 - 2.0 * ssum;
|
||||
Vec3::new(x, y, z).normalize()
|
||||
}
|
||||
Loading…
Reference in a new issue