1use std::cmp::Ordering;
18use std::str::FromStr;
19use std::{
20 fmt::Debug,
21 hash::{Hash, Hasher},
22 ops::RangeInclusive,
23};
24
25use crate::protocol::coordinates::{WireBlockCoordinate, WireChunkCoordinate};
26use anyhow::{bail, ensure, Context, Result};
27use cgmath::{Angle, Deg};
28use rustc_hash::FxHasher;
29
30#[derive(PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
35pub struct BlockCoordinate {
36 pub x: i32,
37 pub y: i32,
38 pub z: i32,
39}
40
41impl Debug for BlockCoordinate {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 f.write_fmt(format_args!("[{}, {}, {}]", self.x, self.y, self.z))
44 }
45}
46impl BlockCoordinate {
47 pub const fn new(x: i32, y: i32, z: i32) -> Self {
48 Self { x, y, z }
49 }
50 #[inline]
51 pub const fn offset(&self) -> ChunkOffset {
52 ChunkOffset {
54 x: self.x.rem_euclid(16) as u8,
55 y: self.y.rem_euclid(16) as u8,
56 z: self.z.rem_euclid(16) as u8,
57 }
58 }
59 #[inline]
60 pub const fn chunk(&self) -> ChunkCoordinate {
61 ChunkCoordinate {
62 x: self.x.div_euclid(16),
63 y: self.y.div_euclid(16),
64 z: self.z.div_euclid(16),
65 }
66 }
67
68 pub fn try_delta(&self, x: i32, y: i32, z: i32) -> Option<BlockCoordinate> {
69 let x = self.x.checked_add(x)?;
70 let y = self.y.checked_add(y)?;
71 let z = self.z.checked_add(z)?;
72
73 Some(BlockCoordinate { x, y, z })
74 }
75}
76impl ToString for BlockCoordinate {
77 fn to_string(&self) -> String {
78 let mut result = String::new();
80 result += self.x.to_string().as_str();
81 result += ",";
82 result += self.y.to_string().as_str();
83 result += ",";
84 result += self.z.to_string().as_str();
85 result
86 }
87}
88impl FromStr for BlockCoordinate {
89 type Err = anyhow::Error;
90
91 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
92 let pieces: Vec<_> = s.split(',').collect();
94 if pieces.len() != 3 {
95 bail!("Wrong number of components");
96 };
97 Ok(BlockCoordinate::new(
98 pieces[0].parse()?,
99 pieces[1].parse()?,
100 pieces[2].parse()?,
101 ))
102 }
103}
104
105impl From<BlockCoordinate> for cgmath::Vector3<f64> {
106 fn from(val: BlockCoordinate) -> Self {
107 cgmath::Vector3::new(val.x as f64, val.y as f64, val.z as f64)
108 }
109}
110
111impl From<BlockCoordinate> for WireBlockCoordinate {
112 fn from(value: BlockCoordinate) -> Self {
113 WireBlockCoordinate {
114 x: value.x,
115 y: value.y,
116 z: value.z,
117 }
118 }
119}
120impl From<&WireBlockCoordinate> for BlockCoordinate {
121 fn from(value: &WireBlockCoordinate) -> Self {
122 BlockCoordinate {
123 x: value.x,
124 y: value.y,
125 z: value.z,
126 }
127 }
128}
129impl From<WireBlockCoordinate> for BlockCoordinate {
130 fn from(value: WireBlockCoordinate) -> Self {
131 (&value).into()
132 }
133}
134
135#[inline]
136fn try_convert(value: f64) -> Result<i32> {
137 ensure!(value.is_finite(), "val was not finite");
138 ensure!(
139 value <= (i32::MAX as f64) && value >= (i32::MIN as f64),
140 "Value is out of bounds as i32"
141 );
142 Ok(value.round() as i32)
143}
144
145impl TryFrom<cgmath::Vector3<f64>> for BlockCoordinate {
146 type Error = anyhow::Error;
147
148 fn try_from(value: cgmath::Vector3<f64>) -> std::result::Result<Self, Self::Error> {
149 Ok(BlockCoordinate {
150 x: try_convert(value.x)?,
151 y: try_convert(value.y)?,
152 z: try_convert(value.z)?,
153 })
154 }
155}
156
157#[derive(PartialEq, Eq, Hash, Clone, Copy)]
161pub struct ChunkOffset {
162 pub x: u8,
163 pub y: u8,
164 pub z: u8,
165}
166impl ChunkOffset {
167 pub const fn new(x: u8, y: u8, z: u8) -> Self {
168 Self { x, y, z }
169 }
170
171 #[cfg(debug_assertions)]
172 #[inline(always)]
173 fn debug_check(&self) {
174 debug_assert!(self.x < 16);
175 debug_assert!(self.y < 16);
176 debug_assert!(self.z < 16);
177 }
178
179 #[cfg(not(debug_assertions))]
180 #[inline(always)]
181 fn debug_check(&self) {}
182
183 #[inline]
184 pub fn as_index(&self) -> usize {
185 self.debug_check();
186 256 * (self.x as usize) + 16 * (self.z as usize) + (self.y as usize)
190 }
191 #[inline]
192 pub fn from_index(index: usize) -> ChunkOffset {
193 assert!(index < 4096);
194 ChunkOffset {
195 y: (index % 16) as u8,
196 z: ((index / 16) % 16) as u8,
197 x: ((index / 256) % 16) as u8,
198 }
199 }
200 pub fn try_delta(&self, x: i8, y: i8, z: i8) -> Option<ChunkOffset> {
201 let x = self.x as i8 + x;
202 let y = self.y as i8 + y;
203 let z = self.z as i8 + z;
204 if !(0..16).contains(&x) || !(0..16).contains(&y) || !(0..16).contains(&z) {
205 None
206 } else {
207 Some(ChunkOffset {
208 x: x as u8,
209 y: y as u8,
210 z: z as u8,
211 })
212 }
213 }
214}
215impl Debug for ChunkOffset {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 f.write_fmt(format_args!("Δ({}, {}, {})", self.x, self.y, self.z))
218 }
219}
220impl PartialOrd<Self> for ChunkOffset {
221 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
222 Some(self.cmp(other))
223 }
224}
225
226impl Ord for ChunkOffset {
227 fn cmp(&self, other: &Self) -> Ordering {
228 self.x
229 .cmp(&other.x)
230 .then(self.z.cmp(&other.z))
231 .then(self.y.cmp(&other.y))
232 }
233}
234
235#[derive(PartialEq, Eq, Hash, Clone, Copy)]
240#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
241pub struct ChunkCoordinate {
242 pub x: i32,
243 pub y: i32,
244 pub z: i32,
245}
246impl ChunkCoordinate {
247 pub fn new(x: i32, y: i32, z: i32) -> Self {
248 let result = Self { x, y, z };
249 debug_assert!(result.is_in_bounds());
250 result
251 }
252
253 pub fn try_new(x: i32, y: i32, z: i32) -> Option<Self> {
254 let result = Self { x, y, z };
255 if result.is_in_bounds() {
256 Some(result)
257 } else {
258 None
259 }
260 }
261
262 pub fn bounds_check(x: i32, y: i32, z: i32) -> bool {
263 let result = Self { x, y, z };
264 result.is_in_bounds()
265 }
266
267 #[inline]
269 pub fn with_offset(&self, offset: ChunkOffset) -> BlockCoordinate {
270 offset.debug_check();
271 BlockCoordinate {
272 x: self.x * 16 + (offset.x as i32),
273 y: self.y * 16 + (offset.y as i32),
274 z: self.z * 16 + (offset.z as i32),
275 }
276 }
277 pub fn manhattan_distance(&self, other: ChunkCoordinate) -> u32 {
279 self.x
280 .abs_diff(other.x)
281 .saturating_add(self.y.abs_diff(other.y))
282 .saturating_add(self.z.abs_diff(other.z))
283 }
284 pub fn l_infinity_norm_distance(&self, other: ChunkCoordinate) -> u32 {
286 self.x
287 .abs_diff(other.x)
288 .max(self.y.abs_diff(other.y))
289 .max(self.z.abs_diff(other.z))
290 }
291 pub fn is_in_bounds(&self) -> bool {
294 const BOUNDS_RANGE: RangeInclusive<i32> = (i32::MIN / 16)..=(i32::MAX / 16);
295 BOUNDS_RANGE.contains(&self.x)
296 && BOUNDS_RANGE.contains(&self.y)
297 && BOUNDS_RANGE.contains(&self.z)
298 }
299 pub fn try_delta(&self, x: i32, y: i32, z: i32) -> Option<ChunkCoordinate> {
301 let x = self.x.checked_add(x)?;
302 let y = self.y.checked_add(y)?;
303 let z = self.z.checked_add(z)?;
304 let candidate = ChunkCoordinate { x, y, z };
305 if candidate.is_in_bounds() {
306 Some(candidate)
307 } else {
308 None
309 }
310 }
311 pub fn hash_u64(&self) -> u64 {
315 let mut hasher = FxHasher::default();
316 self.hash(&mut hasher);
317 hasher.finish()
318 }
319 pub fn coarse_hash_no_y(&self) -> u64 {
324 let mut hasher = FxHasher::default();
325 (self.x >> 4).hash(&mut hasher);
326 (self.z >> 4).hash(&mut hasher);
327 hasher.finish()
328 }
329}
330impl Debug for ChunkCoordinate {
331 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
332 f.write_fmt(format_args!("chunk[{}, {}, {}]", self.x, self.y, self.z))
333 }
334}
335
336impl From<ChunkCoordinate> for WireChunkCoordinate {
337 fn from(value: ChunkCoordinate) -> Self {
338 WireChunkCoordinate {
339 x: value.x,
340 y: value.y,
341 z: value.z,
342 }
343 }
344}
345impl From<&WireChunkCoordinate> for ChunkCoordinate {
346 fn from(value: &WireChunkCoordinate) -> Self {
347 ChunkCoordinate {
348 x: value.x,
349 y: value.y,
350 z: value.z,
351 }
352 }
353}
354impl From<WireChunkCoordinate> for ChunkCoordinate {
355 fn from(value: WireChunkCoordinate) -> Self {
356 (&value).into()
357 }
358}
359
360impl TryFrom<cgmath::Vector3<f64>> for crate::protocol::coordinates::Vec3D {
361 type Error = anyhow::Error;
362
363 fn try_from(value: cgmath::Vector3<f64>) -> std::result::Result<Self, Self::Error> {
364 ensure!(
365 value.x.is_finite() && value.y.is_finite() && value.z.is_finite(),
366 "vec3D contained NaN or inf"
367 );
368 Ok(crate::protocol::coordinates::Vec3D {
369 x: value.x,
370 y: value.y,
371 z: value.z,
372 })
373 }
374}
375impl TryFrom<&crate::protocol::coordinates::Vec3D> for cgmath::Vector3<f64> {
376 type Error = anyhow::Error;
377
378 fn try_from(
379 value: &crate::protocol::coordinates::Vec3D,
380 ) -> std::result::Result<Self, Self::Error> {
381 ensure!(
382 value.x.is_finite() && value.y.is_finite() && value.z.is_finite(),
383 "vec3D contained NaN or inf"
384 );
385 Ok(cgmath::Vector3 {
386 x: value.x,
387 y: value.y,
388 z: value.z,
389 })
390 }
391}
392impl TryFrom<crate::protocol::coordinates::Vec3D> for cgmath::Vector3<f64> {
393 type Error = anyhow::Error;
394
395 fn try_from(value: crate::protocol::coordinates::Vec3D) -> Result<Self> {
396 (&value).try_into()
397 }
398}
399
400impl TryFrom<cgmath::Vector3<f32>> for crate::protocol::coordinates::Vec3F {
401 type Error = anyhow::Error;
402
403 fn try_from(value: cgmath::Vector3<f32>) -> std::result::Result<Self, Self::Error> {
404 ensure!(
405 value.x.is_finite() && value.y.is_finite() && value.z.is_finite(),
406 "vec3D contained NaN or inf"
407 );
408 Ok(crate::protocol::coordinates::Vec3F {
409 x: value.x,
410 y: value.y,
411 z: value.z,
412 })
413 }
414}
415impl TryFrom<&crate::protocol::coordinates::Vec3F> for cgmath::Vector3<f32> {
416 type Error = anyhow::Error;
417
418 fn try_from(
419 value: &crate::protocol::coordinates::Vec3F,
420 ) -> std::result::Result<Self, Self::Error> {
421 ensure!(
422 value.x.is_finite() && value.y.is_finite() && value.z.is_finite(),
423 "vec3D contained NaN or inf"
424 );
425 Ok(cgmath::Vector3 {
426 x: value.x,
427 y: value.y,
428 z: value.z,
429 })
430 }
431}
432impl TryFrom<crate::protocol::coordinates::Vec3F> for cgmath::Vector3<f32> {
433 type Error = anyhow::Error;
434
435 fn try_from(value: crate::protocol::coordinates::Vec3F) -> Result<Self> {
436 (&value).try_into()
437 }
438}
439
440#[derive(Copy, Clone, Debug)]
441pub struct PlayerPositionUpdate {
442 pub position: cgmath::Vector3<f64>,
444 pub velocity: cgmath::Vector3<f64>,
446 pub face_direction: (f64, f64),
448}
449impl PlayerPositionUpdate {
450 pub fn to_proto(&self) -> Result<crate::protocol::game_rpc::PlayerPosition> {
451 Ok(crate::protocol::game_rpc::PlayerPosition {
452 position: Some(self.position.try_into()?),
453 velocity: Some(self.velocity.try_into()?),
454 face_direction: Some(crate::protocol::coordinates::Angles {
455 deg_azimuth: self.face_direction.0,
456 deg_elevation: self.face_direction.1,
457 }),
458 })
459 }
460 pub fn face_unit_vector(&self) -> cgmath::Vector3<f64> {
462 let (sin_az, cos_az) = Deg(self.face_direction.0).sin_cos();
463 let (sin_el, cos_el) = Deg(self.face_direction.1).sin_cos();
464 cgmath::vec3(cos_el * sin_az, sin_el, cos_el * cos_az)
465 }
466}
467impl TryFrom<&crate::protocol::game_rpc::PlayerPosition> for PlayerPositionUpdate {
468 type Error = anyhow::Error;
469
470 fn try_from(value: &crate::protocol::game_rpc::PlayerPosition) -> Result<Self> {
471 let angles = value.face_direction.as_ref().context("missing angles")?;
472 Ok(PlayerPositionUpdate {
473 position: value
474 .position
475 .as_ref()
476 .context("Missing position")?
477 .try_into()?,
478 velocity: value
479 .velocity
480 .as_ref()
481 .context("Missing velocity")?
482 .try_into()?,
483 face_direction: (angles.deg_azimuth, angles.deg_elevation),
484 })
485 }
486}