From 620c0bf287d3dd9d0b9759174b3cb417177de213 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 16 May 2022 04:09:51 -0700 Subject: [PATCH] Replace nalgebra-glm with vek Vek has been pleasant to use in practice. nalgebra is slow to compile and its documentation is difficult to traverse. Vek also comes with its own AABB impl, so we use that instead. --- Cargo.toml | 2 +- examples/basic.rs | 4 +- src/aabb.rs | 176 ---------------------------------------------- src/chunk.rs | 12 +--- src/client.rs | 66 +++++++++-------- src/entity.rs | 38 +++++----- src/lib.rs | 4 +- src/packets.rs | 26 +++---- src/protocol.rs | 45 ++++++++++-- src/util.rs | 27 +++++++ 10 files changed, 136 insertions(+), 264 deletions(-) delete mode 100644 src/aabb.rs diff --git a/Cargo.toml b/Cargo.toml index 70ce7f8..1733f53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ flume = "0.10" futures = "0.3" hematite-nbt = "0.5" log = "0.4" -nalgebra-glm = "0.17" num = "0.4" parking_lot = "0.12" paste = "1" @@ -39,6 +38,7 @@ sha2 = "0.10" thiserror = "1" tokio = { version = "1", features = ["full"] } uuid = "1" +vek = "0.15" [dependencies.reqwest] version = "0.11" diff --git a/examples/basic.rs b/examples/basic.rs index cbd9e32..cbfcff1 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -7,8 +7,8 @@ use valence::client::GameMode; use valence::config::{Config, ServerListPing}; use valence::text::Color; use valence::{ - async_trait, ChunkPos, ClientMut, DimensionId, Server, ShutdownResult, Text, - TextFormat, WorldId, WorldsMut, + async_trait, ChunkPos, ClientMut, DimensionId, Server, ShutdownResult, Text, TextFormat, + WorldId, WorldsMut, }; pub fn main() -> ShutdownResult { diff --git a/src/aabb.rs b/src/aabb.rs deleted file mode 100644 index 184656c..0000000 --- a/src/aabb.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::cmp::Ordering; -use std::hash::{Hash, Hasher}; - -use num::cast::AsPrimitive; - -use crate::glm::{self, Number, RealNumber, TVec, TVec3}; - -/// An Axis-aligned bounding box in an arbitrary dimension, defined by its -/// minimum and maximum corners. -/// -/// This type maintains the invariant that `min <= max` componentwise. -/// -/// The generic type `T` can be an integer, which is useful for AABBs on grids. -#[derive(Clone, Copy, Debug)] -pub struct Aabb { - min: TVec, - max: TVec, -} - -impl Aabb { - pub fn new(p0: impl Into>, p1: impl Into>) -> Self { - let p0 = p0.into(); - let p1 = p1.into(); - Self { - min: glm::min2(&p0, &p1), - max: glm::max2(&p0, &p1), - } - } - - pub fn point(pos: impl Into>) -> Self { - let pos = pos.into(); - Self { min: pos, max: pos } - } - - pub fn min(&self) -> TVec { - self.min - } - - pub fn max(&self) -> TVec { - self.max - } - - pub fn dimensions(&self) -> TVec { - self.max - self.min - } - - /// Moves this AABB by some vector. - pub fn translate(&self, v: impl Into>) -> Self { - let v = v.into(); - Self { - min: self.min + v, - max: self.max + v, - } - } - - /// Calculates the AABB union, which is the smallest AABB completely - /// encompassing both AABBs. - pub fn union(&self, other: Self) -> Self { - Self { - min: glm::min2(&self.min, &other.min), - max: glm::max2(&self.max, &other.max), - } - } - - pub fn collides_with_aabb(&self, other: &Self) -> bool { - let l = glm::less_than_equal(&self.min, &other.max); - let r = glm::greater_than_equal(&self.max, &other.min); - glm::all(&l.zip_map(&r, |l, r| l && r)) - } -} - -impl Aabb -where - i32: AsPrimitive, -{ - /// Returns the center (centroid) of this AABB. - pub fn center(&self) -> TVec { - (self.min + self.max).map(|c| c / 2.as_()) - } -} - -impl Aabb { - /// Construct an AABB from a center (centroid) and the dimensions of the box - /// along each axis. - pub fn from_center_and_dimensions( - center: impl Into>, - dims: impl Into>, - ) -> Self { - let half = dims.into() * T::from_subset(&0.5); - let center = center.into(); - Self { - min: center - half, - max: center + half, - } - } - - pub fn collides_with_sphere( - &self, - center: impl Into>, - radius: impl Into, - ) -> bool { - self.distance_to_point(center.into()) <= radius.into() - } - - pub fn distance_to_point(&self, p: impl Into>) -> T { - let p = p.into(); - glm::distance(&p, &glm::clamp_vec(&p, &self.min, &self.max)) - } -} - -impl Aabb -where - i32: AsPrimitive, -{ - pub fn surface_area(&self) -> T { - let d = self.dimensions(); - (d.x * d.y + d.y * d.z + d.z * d.x) * 2.as_() - } -} - -impl Aabb { - /// Constructs an AABB from a position and the dimensions of the box along - /// each axis. The position is the center of the bottom face of the AABB. - pub fn from_bottom_and_dimensions( - bottom: impl Into>, - dims: impl Into>, - ) -> Self { - let dims = dims.into(); - Self::from_center_and_dimensions(bottom, dims).translate([ - T::from_subset(&0.0), - dims.y * T::from_subset(&0.5), - T::from_subset(&0.0), - ]) - } -} - -impl Default for Aabb { - fn default() -> Self { - let d = T::default(); - Self::new([d; D], [d; D]) - } -} - -impl PartialEq for Aabb { - fn eq(&self, other: &Self) -> bool { - self.min == other.min && self.max == other.max - } -} - -impl Eq for Aabb {} - -impl PartialOrd for Aabb { - fn partial_cmp(&self, other: &Self) -> Option { - match self.min.partial_cmp(&other.min) { - Some(Ordering::Equal) => self.max.partial_cmp(&other.max), - ord => return ord, - } - } -} - -impl Hash for Aabb { - fn hash(&self, state: &mut H) { - self.min.hash(state); - self.max.hash(state); - } -} - -// TODO: impl Ord for Aabb -//impl Ord for Aabb { -// fn cmp(&self, other: &Self) -> std::cmp::Ordering { -// match self.min.cmp(&other.min) { -// Ordering::Equal => self.max.cmp(&other.max), -// ord => ord, -// } -// } -//} diff --git a/src/chunk.rs b/src/chunk.rs index 603fae1..2c4d3bf 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -5,13 +5,12 @@ use std::io::Write; use std::iter::FusedIterator; use std::ops::Deref; -use bitvec::bitvec; use bitvec::vec::BitVec; use num::Integer; use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; +use vek::Vec2; use crate::block::BlockState; -use crate::glm::DVec2; use crate::packets::play::{ BlockChange, ChunkDataAndUpdateLight, ChunkDataHeightmaps, ClientPlayPacket, MultiBlockChange, }; @@ -292,15 +291,6 @@ impl ChunkPos { pub const fn new(x: i32, z: i32) -> Self { Self { x, z } } - - /// Returns the chunk position of the chunk that the given coordinates are - /// contained within. - pub fn from_xz(xz: DVec2) -> Self { - Self { - x: (xz.x / 16.0) as i32, - z: (xz.y / 16.0) as i32, - } - } } impl From<(i32, i32)> for ChunkPos { diff --git a/src/client.rs b/src/client.rs index 544f04b..6275af2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,9 +3,9 @@ use std::iter::FusedIterator; use std::ops::Deref; use flume::{Receiver, Sender, TrySendError}; -use glm::DVec3; use rayon::iter::ParallelIterator; use uuid::Uuid; +use vek::Vec3; use crate::block_pos::BlockPos; use crate::config::{ @@ -26,9 +26,7 @@ use crate::server::ServerPacketChannels; use crate::slotmap::{Key, SlotMap}; use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance}; use crate::var_int::VarInt; -use crate::{ - glm, ident, ChunkPos, Chunks, Entities, EntityId, Server, Text, Ticks, LIBRARY_NAMESPACE, -}; +use crate::{ident, ChunkPos, Chunks, Entities, EntityId, Server, Text, Ticks, LIBRARY_NAMESPACE}; pub struct Clients { sm: SlotMap, @@ -117,8 +115,8 @@ pub struct Client { username: String, uuid: Uuid, on_ground: bool, - new_position: DVec3, - old_position: DVec3, + new_position: Vec3, + old_position: Vec3, /// Measured in degrees yaw: f32, /// Measured in degrees @@ -181,8 +179,8 @@ impl Client { username, uuid, on_ground: false, - new_position: DVec3::default(), - old_position: DVec3::default(), + new_position: Vec3::default(), + old_position: Vec3::default(), yaw: 0.0, pitch: 0.0, teleported_this_tick: false, @@ -216,7 +214,7 @@ impl Client { self.uuid } - pub fn position(&self) -> DVec3 { + pub fn position(&self) -> Vec3 { self.new_position } @@ -262,7 +260,7 @@ impl<'a> ClientMut<'a> { ClientMut(self.0) } - pub fn teleport(&mut self, pos: impl Into, yaw: f32, pitch: f32) { + pub fn teleport(&mut self, pos: impl Into>, yaw: f32, pitch: f32) { self.0.new_position = pos.into(); self.0.yaw = yaw; self.0.pitch = pitch; @@ -425,7 +423,10 @@ impl<'a> ClientMut<'a> { .map_or(2, |s| s.view_distance) .min(self.new_max_view_distance); - let center = ChunkPos::from_xz(self.0.new_position.xz()); + let center = ChunkPos::new( + (self.new_position.x / 16.0) as i32, + (self.new_position.z / 16.0) as i32, + ); // Send the update view position packet if the client changes the chunk section // they're in. @@ -483,20 +484,19 @@ impl<'a> ClientMut<'a> { // This is done after the chunks are loaded so that the "downloading terrain" // screen is closed at the appropriate time. - - // TODO: temporarily broken - // if self.0.teleported_this_tick { - // self.0.teleported_this_tick = false; - // self.send_packet(dbg!(PlayerPositionAndLook { - // position: self.new_position, - // yaw: self.yaw, - // pitch: self.pitch, - // flags: PlayerPositionAndLookFlags::new(false, false, false, false, false), - // teleport_id: VarInt((self.teleport_id_counter - 1) as i32), - // dismount_vehicle: false, - // })); - // } + if self.0.teleported_this_tick { + self.0.teleported_this_tick = false; + + self.send_packet(PlayerPositionAndLook { + position: self.new_position, + yaw: self.yaw, + pitch: self.pitch, + flags: PlayerPositionAndLookFlags::new(false, false, false, false, false), + teleport_id: VarInt((self.teleport_id_counter - 1) as i32), + dismount_vehicle: false, + }); + } let mut entities_to_unload = Vec::new(); @@ -504,9 +504,7 @@ impl<'a> ClientMut<'a> { // longer visible. self.0.loaded_entities.retain(|&id| { if let Some(entity) = entities.get(id) { - if glm::distance(&self.0.new_position, &entity.position()) - <= view_dist as f64 * 16.0 - { + if self.0.new_position.distance(entity.position()) <= view_dist as f64 * 16.0 { todo!("update entity"); return true; } @@ -524,7 +522,7 @@ impl<'a> ClientMut<'a> { // Spawn new entities within the view distance. for (id, entity) in entities.iter() { - if glm::distance(&self.position(), &entity.position()) <= view_dist as f64 * 16.0 + if self.position().distance(entity.position()) <= view_dist as f64 * 16.0 && entity.typ() != EntityType::Marker && self.0.loaded_entities.insert(id) { @@ -548,7 +546,7 @@ impl<'a> ClientMut<'a> { fn handle_movement_packet( client: &mut Client, - new_position: DVec3, + new_position: Vec3, new_yaw: f32, new_pitch: f32, new_on_ground: bool, @@ -556,8 +554,8 @@ impl<'a> ClientMut<'a> { if client.pending_teleports == 0 { let event = Event::Movement { position: client.new_position, - yaw_degrees: client.yaw, - pitch_degrees: client.pitch, + yaw: client.yaw, + pitch: client.pitch, on_ground: client.on_ground, }; @@ -693,9 +691,9 @@ pub enum Event { /// The client has moved. The values in this variant are the previous /// position and look. Movement { - position: DVec3, - yaw_degrees: f32, - pitch_degrees: f32, + position: Vec3, + yaw: f32, + pitch: f32, on_ground: bool, }, } diff --git a/src/entity.rs b/src/entity.rs index d558314..b4774ed 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -9,18 +9,17 @@ use std::ops::Deref; use bitfield_struct::bitfield; use rayon::iter::ParallelIterator; use uuid::Uuid; +use vek::{Aabb, Vec3}; use crate::byte_angle::ByteAngle; -use crate::chunk::ChunkPos; -use crate::glm::{DVec3, I16Vec3, Vec3}; use crate::packets::play::{ ClientPlayPacket, EntityMetadata, SpawnEntity, SpawnExperienceOrb, SpawnLivingEntity, SpawnPainting, SpawnPlayer, }; use crate::protocol::ReadToEnd; use crate::slotmap::{Key, SlotMap}; +use crate::util::aabb_from_bottom_and_size; use crate::var_int::VarInt; -use crate::{Aabb, WorldId}; pub struct Entities { sm: SlotMap, @@ -103,8 +102,8 @@ impl<'a> EntitiesMut<'a> { let entity = EntityId(self.0.sm.insert(Entity { flags: EntityFlags(0), meta: EntityMeta::new(EntityType::Marker), - new_position: DVec3::default(), - old_position: DVec3::default(), + new_position: Vec3::default(), + old_position: Vec3::default(), yaw: 0.0, pitch: 0.0, head_yaw: 0.0, @@ -181,13 +180,13 @@ impl EntityId { pub struct Entity { flags: EntityFlags, meta: EntityMeta, - new_position: DVec3, - old_position: DVec3, + new_position: Vec3, + old_position: Vec3, yaw: f32, pitch: f32, head_yaw: f32, head_pitch: f32, - velocity: Vec3, + velocity: Vec3, uuid: Uuid, } @@ -230,13 +229,13 @@ impl Entity { } /// Returns the position of this entity in the world it inhabits. - pub fn position(&self) -> DVec3 { + pub fn position(&self) -> Vec3 { self.new_position } /// Returns the position of this entity as it existed at the end of the /// previous tick. - pub fn old_position(&self) -> DVec3 { + pub fn old_position(&self) -> Vec3 { self.old_position } @@ -261,7 +260,7 @@ impl Entity { } /// Gets the velocity of this entity in meters per second. - pub fn velocity(&self) -> Vec3 { + pub fn velocity(&self) -> Vec3 { self.velocity } @@ -369,7 +368,7 @@ impl Entity { } } - pub fn hitbox(&self) -> Aabb { + pub fn hitbox(&self) -> Aabb { let dims = match &self.meta { EntityMeta::AreaEffectCloud(e) => [ e.get_radius() as f64 * 2.0, @@ -504,13 +503,13 @@ impl Entity { EntityMeta::FishingBobber(_) => [0.25, 0.25, 0.25], }; - Aabb::from_bottom_and_dimensions(self.new_position, dims) + aabb_from_bottom_and_size(self.new_position, dims.into()) } } -fn velocity_to_packet_units(vel: Vec3) -> I16Vec3 { +fn velocity_to_packet_units(vel: Vec3) -> Vec3 { // The saturating cast to i16 is desirable. - vel.map(|v| (v * 400.0) as i16) + (vel * 400.0).as_() } impl<'a> EntityMut<'a> { @@ -534,7 +533,7 @@ impl<'a> EntityMut<'a> { } /// Sets the position of this entity in the world it inhabits. - pub fn set_position(&mut self, pos: impl Into) { + pub fn set_position(&mut self, pos: impl Into>) { self.0.new_position = pos.into(); } @@ -570,9 +569,10 @@ impl<'a> EntityMut<'a> { } } - pub fn set_velocity(&mut self, velocity: Vec3) { - self.0.velocity = velocity; - if velocity_to_packet_units(self.velocity) != velocity_to_packet_units(velocity) { + pub fn set_velocity(&mut self, velocity: impl Into>) { + let new_vel = velocity.into(); + self.0.velocity = new_vel; + if velocity_to_packet_units(self.velocity) != velocity_to_packet_units(new_vel) { self.0.flags.set_velocity_modified(true); } } diff --git a/src/lib.rs b/src/lib.rs index ec317b7..af23b74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ // missing_docs )] -mod aabb; pub mod block; mod block_pos; mod byte_angle; @@ -27,7 +26,6 @@ mod var_int; mod var_long; pub mod world; -pub use aabb::Aabb; pub use async_trait::async_trait; pub use block_pos::BlockPos; pub use chunk::{Chunk, ChunkPos, Chunks, ChunksMut}; @@ -39,7 +37,7 @@ pub use server::{start_server, NewClientData, Server, ShutdownResult}; pub use text::{Text, TextFormat}; pub use uuid::Uuid; pub use world::{WorldId, WorldMut, WorldRef, Worlds, WorldsMut}; -pub use {nalgebra_glm as glm, nbt, uuid}; +pub use {nbt, uuid, vek}; /// The Minecraft protocol version that this library targets. pub const PROTOCOL_VERSION: i32 = 758; diff --git a/src/packets.rs b/src/packets.rs index d552ced..65d574d 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -13,10 +13,10 @@ use num::{One, Zero}; use paste::paste; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use vek::{Vec2, Vec3}; use crate::block_pos::BlockPos; use crate::byte_angle::ByteAngle; -use crate::glm::{DVec3, I16Vec3, Vec3}; use crate::identifier::Identifier; use crate::protocol::{BoundedArray, BoundedInt, BoundedString, Decode, Encode, Nbt, ReadToEnd}; use crate::var_int::VarInt; @@ -445,18 +445,18 @@ pub mod play { entity_id: VarInt, object_uuid: Uuid, typ: VarInt, - position: DVec3, + position: Vec3, pitch: ByteAngle, yaw: ByteAngle, data: i32, - velocity: I16Vec3, + velocity: Vec3, } } def_struct! { SpawnExperienceOrb 0x01 { entity_id: VarInt, - position: DVec3, + position: Vec3, count: i16, } } @@ -466,11 +466,11 @@ pub mod play { entity_id: VarInt, entity_uuid: Uuid, typ: VarInt, - position: DVec3, + position: Vec3, yaw: ByteAngle, pitch: ByteAngle, head_pitch: ByteAngle, - velocity: I16Vec3, + velocity: Vec3, } } @@ -497,7 +497,7 @@ pub mod play { SpawnPlayer 0x04 { entity_id: VarInt, player_uuid: Uuid, - position: DVec3, + position: Vec3, yaw: ByteAngle, pitch: ByteAngle, } @@ -966,7 +966,7 @@ pub mod play { def_struct! { PlayerPositionAndLook 0x38 { - position: DVec3, + position: Vec3, yaw: f32, pitch: f32, flags: PlayerPositionAndLookFlags, @@ -1290,7 +1290,7 @@ pub mod play { def_struct! { InteractAtData { - target: Vec3, + target: Vec3, hand: Hand, } } @@ -1317,7 +1317,7 @@ pub mod play { def_struct! { PlayerPosition 0x11 { - position: DVec3, + position: Vec3, on_ground: bool, } } @@ -1325,7 +1325,7 @@ pub mod play { def_struct! { PlayerPositionAndRotation 0x12 { // Absolute position - position: DVec3, + position: Vec3, /// Absolute rotation on X axis in degrees. yaw: f32, /// Absolute rotation on Y axis in degrees. @@ -1353,7 +1353,7 @@ pub mod play { def_struct! { VehicleMoveServerbound 0x15 { /// Absolute position - position: DVec3, + position: Vec3, /// Degrees yaw: f32, /// Degrees @@ -1669,7 +1669,7 @@ pub mod play { hand: Hand, location: BlockPos, face: BlockFace, - cursor_pos: Vec3, + cursor_pos: Vec3, head_inside_block: bool, } } diff --git a/src/protocol.rs b/src/protocol.rs index 20d43ee..9d25027 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -5,9 +5,9 @@ use anyhow::{anyhow, ensure, Context}; use arrayvec::ArrayVec; use bitvec::prelude::*; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; -use nalgebra_glm::{Number, TVec}; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use vek::{Vec2, Vec3, Vec4}; use crate::var_int::VarInt; use crate::EntityId; @@ -372,15 +372,50 @@ impl Decode for [T; N] { } } -impl Encode for TVec { +impl Encode for Vec2 { fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { - encode_array_bounded(self.as_slice(), N, N, w) + self.x.encode(w)?; + self.y.encode(w) } } -impl Decode for TVec { +impl Encode for Vec3 { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + self.x.encode(w)?; + self.y.encode(w)?; + self.z.encode(w) + } +} + +impl Encode for Vec4 { + fn encode(&self, w: &mut impl Write) -> anyhow::Result<()> { + self.x.encode(w)?; + self.y.encode(w)?; + self.z.encode(w)?; + self.w.encode(w) + } +} + +impl Decode for Vec2 { fn decode(r: &mut impl Read) -> anyhow::Result { - Ok(<[T; N]>::decode(r)?.into()) + Ok(Vec2::new(T::decode(r)?, T::decode(r)?)) + } +} + +impl Decode for Vec3 { + fn decode(r: &mut impl Read) -> anyhow::Result { + Ok(Vec3::new(T::decode(r)?, T::decode(r)?, T::decode(r)?)) + } +} + +impl Decode for Vec4 { + fn decode(r: &mut impl Read) -> anyhow::Result { + Ok(Vec4::new( + T::decode(r)?, + T::decode(r)?, + T::decode(r)?, + T::decode(r)?, + )) } } diff --git a/src/util.rs b/src/util.rs index 6a21873..de4a5bf 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,9 @@ use std::iter::FusedIterator; +use num::cast::AsPrimitive; +use num::Float; +use vek::{Aabb, Extent3, Vec3}; + use crate::ChunkPos; /// Returns true if the given string meets the criteria for a valid Minecraft @@ -26,3 +30,26 @@ pub fn is_chunk_in_view_distance(center: ChunkPos, other: ChunkPos, distance: u8 (center.x as f64 - other.x as f64).powi(2) + (center.z as f64 - other.z as f64).powi(2) <= (distance as f64 + EXTRA_RADIUS as f64).powi(2) } + +pub(crate) fn aabb_from_bottom_and_size(bottom: Vec3, size: Vec3) -> Aabb +where + T: Float + 'static, + f64: AsPrimitive, +{ + let aabb = Aabb { + min: Vec3::new( + bottom.x - size.x / 2.0.as_(), + bottom.y, + bottom.z - size.z / 2.0.as_(), + ), + max: Vec3::new( + bottom.x + size.x / 2.0.as_(), + bottom.y, + bottom.z + size.z / 2.0.as_(), + ), + }; + + debug_assert!(aabb.is_valid()); + + aabb +}