Complete the cow sphere™

This commit is contained in:
Ryan 2022-05-18 03:05:10 -07:00
parent 1570c95ac8
commit f875ed07ce
8 changed files with 118 additions and 51 deletions

View file

@ -68,7 +68,7 @@ impl Config for Game {
} }
fn init(&self, _server: &Server, mut worlds: WorldsMut) { fn init(&self, _server: &Server, mut worlds: WorldsMut) {
let world_id = worlds.create(DimensionId::default()); let world_id = worlds.create(DimensionId::default()).0;
let mut world = worlds.get_mut(world_id).unwrap(); let mut world = worlds.get_mut(world_id).unwrap();
let size = 5; let size = 5;

View file

@ -1,15 +1,18 @@
use std::f64::consts::TAU;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use log::LevelFilter; use log::LevelFilter;
use valence::block::BlockState;
use valence::client::GameMode; use valence::client::GameMode;
use valence::config::{Config, ServerListPing}; use valence::config::{Config, ServerListPing};
use valence::text::Color; use valence::text::Color;
use valence::util::to_yaw_and_pitch;
use valence::{ use valence::{
async_trait, ChunkPos, ClientMut, DimensionId, EntityType, Server, ShutdownResult, Text, async_trait, ClientMut, DimensionId, EntityId, EntityType, Server, ShutdownResult, Text,
TextFormat, WorldId, WorldsMut, TextFormat, WorldId, WorldsMut,
}; };
use vek::{Mat3, Vec3};
pub fn main() -> ShutdownResult { pub fn main() -> ShutdownResult {
env_logger::Builder::new() env_logger::Builder::new()
@ -19,11 +22,13 @@ pub fn main() -> ShutdownResult {
valence::start_server(Game { valence::start_server(Game {
player_count: AtomicUsize::new(0), player_count: AtomicUsize::new(0),
cows: Mutex::new(Vec::new()),
}) })
} }
struct Game { struct Game {
player_count: AtomicUsize, player_count: AtomicUsize,
cows: Mutex<Vec<EntityId>>,
} }
const MAX_PLAYERS: usize = 10; const MAX_PLAYERS: usize = 10;
@ -68,8 +73,7 @@ impl Config for Game {
} }
fn init(&self, _server: &Server, mut worlds: WorldsMut) { fn init(&self, _server: &Server, mut worlds: WorldsMut) {
let world_id = worlds.create(DimensionId::default()); let mut world = worlds.create(DimensionId::default()).1;
let mut world = worlds.get_mut(world_id).unwrap();
world.meta.set_flat(true); world.meta.set_flat(true);
let size = 5; let size = 5;
@ -79,13 +83,11 @@ impl Config for Game {
} }
} }
let entity_id = world.entities.create(); self.cows.lock().unwrap().extend((0..200).map(|_| {
let mut entity = world.entities.get_mut(entity_id).unwrap(); let (id, mut e) = world.entities.create();
e.set_type(EntityType::Cow);
entity.set_type(EntityType::Cow); id
entity.set_position([0.0, 100.0, 0.0]); }));
//entity.set_yaw(30.0);
//entity.set_pitch(0.0);
} }
fn update(&self, server: &Server, mut worlds: WorldsMut) { fn update(&self, server: &Server, mut worlds: WorldsMut) {
@ -105,15 +107,54 @@ impl Config for Game {
} }
}); });
for (_, mut e) in world.entities.iter_mut() { let time = server.current_tick() as f64 / server.tick_rate() as f64;
let time = server.current_tick() as f64 / server.tick_rate() as f64;
if e.typ() == EntityType::Cow { let rot = Mat3::rotation_x(time * TAU * 0.1)
e.set_position(e.position() + [0.0, 0.0, 0.02]); .rotated_y(time * TAU * 0.2)
let yaw = (time % 1.0 * 360.0) as f32; .rotated_z(time * TAU * 0.3);
e.set_yaw(yaw);
e.set_head_yaw(yaw); let cows = self.cows.lock().unwrap();
} let cow_count = cows.len();
let radius = 6.0 + ((time * TAU / 2.5).sin() + 1.0) / 2.0 * 10.0;
// TODO: use eye position.
let player_pos = world
.clients
.iter()
.next()
.map(|c| c.1.position())
.unwrap_or_default();
for (cow_id, p) in cows.iter().cloned().zip(fibonacci_spiral(cow_count)) {
let mut cow = world.entities.get_mut(cow_id).expect("missing cow");
let rotated = p * rot;
let transformed = rotated * radius + [0.0, 100.0, 0.0];
let yaw = f32::atan2(rotated.z as f32, rotated.x as f32).to_degrees() - 90.0;
let (looking_yaw, looking_pitch) =
to_yaw_and_pitch((player_pos - transformed).normalized());
cow.set_position(transformed);
cow.set_yaw(yaw);
cow.set_pitch(looking_pitch);
cow.set_head_yaw(looking_yaw);
} }
} }
} }
/// Distributes N points on the surface of a unit sphere.
fn fibonacci_spiral(n: usize) -> impl Iterator<Item = Vec3<f64>> {
(0..n).map(move |i| {
let golden_ratio = (1.0 + 5_f64.sqrt()) / 2.0;
// Map to unit square
let x = i as f64 / golden_ratio % 1.0;
let y = i as f64 / n as f64;
// Map from unit square to unit sphere.
let theta = x * TAU;
let phi = (1.0 - 2.0 * y).acos();
Vec3::new(theta.cos() * phi.sin(), theta.sin() * phi.sin(), phi.cos())
})
}

View file

@ -77,8 +77,9 @@ impl<'a> ClientsMut<'a> {
ClientsMut(self.0) ClientsMut(self.0)
} }
pub(crate) fn create(&mut self, client: Client) -> ClientId { pub(crate) fn create(&mut self, client: Client) -> (ClientId, ClientMut) {
ClientId(self.0.sm.insert(client)) let (id, client) = self.0.sm.insert(client);
(ClientId(id), ClientMut(client))
} }
pub fn delete(&mut self, client: ClientId) -> bool { pub fn delete(&mut self, client: ClientId) -> bool {

View file

@ -98,7 +98,7 @@ pub trait Config: Any + Send + Sync + UnwindSafe + RefUnwindSafe {
/// An unspecified value is returned that should be adequate in most /// An unspecified value is returned that should be adequate in most
/// situations. /// situations.
fn outgoing_packet_capacity(&self) -> usize { fn outgoing_packet_capacity(&self) -> usize {
128 512
} }
/// Called once at startup to get a handle to the tokio runtime the server /// Called once at startup to get a handle to the tokio runtime the server

View file

@ -81,13 +81,9 @@ impl<'a> EntitiesMut<'a> {
/// ///
/// To actually see the new entity, set its position to somewhere nearby and /// To actually see the new entity, set its position to somewhere nearby and
/// [set its type](EntityData::set_type) to something visible. /// [set its type](EntityData::set_type) to something visible.
pub fn create(&mut self) -> EntityId { pub fn create(&mut self) -> (EntityId, EntityMut) {
loop { self.create_with_uuid(Uuid::from_bytes(rand::random()))
let uuid = Uuid::from_bytes(rand::random()); .expect("UUID collision")
if let Some(entity) = self.create_with_uuid(uuid) {
return entity;
}
}
} }
/// Like [`create`](Entities::create), but requires specifying the new /// Like [`create`](Entities::create), but requires specifying the new
@ -95,11 +91,11 @@ impl<'a> EntitiesMut<'a> {
/// ///
/// The provided UUID must not conflict with an existing entity UUID in this /// The provided UUID must not conflict with an existing entity UUID in this
/// world. If it does, `None` is returned and the entity is not spawned. /// world. If it does, `None` is returned and the entity is not spawned.
pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<EntityId> { pub fn create_with_uuid(&mut self, uuid: Uuid) -> Option<(EntityId, EntityMut)> {
match self.0.uuid_to_entity.entry(uuid) { match self.0.uuid_to_entity.entry(uuid) {
Entry::Occupied(_) => None, Entry::Occupied(_) => None,
Entry::Vacant(ve) => { Entry::Vacant(ve) => {
let entity = EntityId(self.0.sm.insert(Entity { let (id, entity) = self.0.sm.insert(Entity {
flags: EntityFlags(0), flags: EntityFlags(0),
meta: EntityMeta::new(EntityType::Marker), meta: EntityMeta::new(EntityType::Marker),
new_position: Vec3::default(), new_position: Vec3::default(),
@ -107,16 +103,15 @@ impl<'a> EntitiesMut<'a> {
yaw: 0.0, yaw: 0.0,
pitch: 0.0, pitch: 0.0,
head_yaw: 0.0, head_yaw: 0.0,
head_pitch: 0.0,
velocity: Vec3::default(), velocity: Vec3::default(),
uuid, uuid,
})); });
ve.insert(entity); ve.insert(EntityId(id));
// TODO: insert into partition. // TODO: insert into partition.
Some(entity) Some((EntityId(id), EntityMut(entity)))
} }
} }
} }
@ -188,7 +183,6 @@ pub struct Entity {
yaw: f32, yaw: f32,
pitch: f32, pitch: f32,
head_yaw: f32, head_yaw: f32,
head_pitch: f32,
velocity: Vec3<f32>, velocity: Vec3<f32>,
uuid: Uuid, uuid: Uuid,
} }

View file

@ -76,11 +76,11 @@ impl<T> SlotMap<T> {
self.count as usize self.count as usize
} }
pub fn insert(&mut self, val: T) -> Key { pub fn insert(&mut self, val: T) -> (Key, &mut T) {
self.insert_with(|_| val) self.insert_with(|_| val)
} }
pub fn insert_with(&mut self, f: impl FnOnce(Key) -> T) -> Key { pub fn insert_with(&mut self, f: impl FnOnce(Key) -> T) -> (Key, &mut T) {
assert!(self.count < u32::MAX, "SlotMap: too many items inserted"); assert!(self.count < u32::MAX, "SlotMap: too many items inserted");
if self.next_free_head == self.slots.len() as u32 { if self.next_free_head == self.slots.len() as u32 {
@ -93,7 +93,12 @@ impl<T> SlotMap<T> {
value: f(key), value: f(key),
version: key.version(), version: key.version(),
}); });
key
let value = match self.slots.last_mut() {
Some(Slot::Occupied { value, .. }) => value,
_ => unreachable!(),
};
(key, value)
} else { } else {
let slot = &mut self.slots[self.next_free_head as usize]; let slot = &mut self.slots[self.next_free_head as usize];
@ -109,9 +114,15 @@ impl<T> SlotMap<T> {
version: key.version(), version: key.version(),
}; };
let value = match slot {
Slot::Occupied { value, .. } => value,
Slot::Free { .. } => unreachable!(),
};
self.next_free_head = next_free; self.next_free_head = next_free;
self.count += 1; self.count += 1;
key
(key, value)
} }
} }
@ -234,14 +245,14 @@ mod tests {
fn insert_remove() { fn insert_remove() {
let mut sm = SlotMap::new(); let mut sm = SlotMap::new();
let k0 = sm.insert(10); let k0 = sm.insert(10).0;
let k1 = sm.insert(20); let k1 = sm.insert(20).0;
let k2 = sm.insert(30); let k2 = sm.insert(30).0;
assert_eq!(sm.remove(k1), Some(20)); assert_eq!(sm.remove(k1), Some(20));
assert_eq!(sm.get(k1), None); assert_eq!(sm.get(k1), None);
assert_eq!(sm.get(k2), Some(&30)); assert_eq!(sm.get(k2), Some(&30));
let k3 = sm.insert(40); let k3 = sm.insert(40).0;
assert_eq!(sm.get(k0), Some(&10)); assert_eq!(sm.get(k0), Some(&10));
assert_eq!(sm.get_mut(k3), Some(&mut 40)); assert_eq!(sm.get_mut(k3), Some(&mut 40));
assert_eq!(sm.remove(k0), Some(10)); assert_eq!(sm.remove(k0), Some(10));
@ -254,9 +265,9 @@ mod tests {
fn retain() { fn retain() {
let mut sm = SlotMap::new(); let mut sm = SlotMap::new();
let k0 = sm.insert(10); let k0 = sm.insert(10).0;
let k1 = sm.insert(20); let k1 = sm.insert(20).0;
let k2 = sm.insert(30); let k2 = sm.insert(30).0;
sm.retain(|k, _| k == k1); sm.retain(|k, _| k == k1);

View file

@ -53,3 +53,21 @@ where
aabb aabb
} }
/// Takes a normalized direction vector and returns a (yaw, pitch) tuple in degrees.
///
/// This function is the inverse of [`from_yaw_and_pitch`].
pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f32, f32) {
debug_assert!(d.is_normalized(), "the given vector should be normalized");
let yaw = f32::atan2(d.z as f32, d.x as f32).to_degrees() - 90.0;
let pitch = -(d.y as f32).asin().to_degrees();
(yaw, pitch)
}
/// Takes yaw and pitch angles (in degrees) and returns a normalized direction vector.
///
/// This function is the inverse of [`to_yaw_and_pitch`].
pub fn from_yaw_and_pitch(yaw: f32, pitch: f32) -> Vec3<f64> {
todo!()
}

View file

@ -55,8 +55,8 @@ impl<'a> WorldsMut<'a> {
WorldsMut(self.0) WorldsMut(self.0)
} }
pub fn create(&mut self, dim: DimensionId) -> WorldId { pub fn create(&mut self, dim: DimensionId) -> (WorldId, WorldMut) {
WorldId(self.0.sm.insert(World { let (id, world) = self.0.sm.insert(World {
clients: Clients::new(), clients: Clients::new(),
entities: Entities::new(), entities: Entities::new(),
chunks: Chunks::new( chunks: Chunks::new(
@ -67,7 +67,9 @@ impl<'a> WorldsMut<'a> {
dimension: dim, dimension: dim,
is_flat: false, is_flat: false,
}, },
})) });
(WorldId(id), WorldMut::new(world))
} }
/// Deletes a world from the server. Any [`WorldId`] referring to the /// Deletes a world from the server. Any [`WorldId`] referring to the