use std::iter::FusedIterator;
use num::cast::AsPrimitive;
use num::Float;
use vek::{Aabb, Vec3};
use crate::chunk_pos::ChunkPos;
pub fn valid_username(s: &str) -> bool {
(3..=16).contains(&s.len())
&& s.chars()
.all(|c| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_'))
}
const EXTRA_RADIUS: i32 = 3;
pub fn chunks_in_view_distance(
center: ChunkPos,
distance: u8,
) -> impl FusedIterator<Item = ChunkPos> {
let dist = distance as i32 + EXTRA_RADIUS;
(center.z - dist..=center.z + dist)
.flat_map(move |z| (center.x - dist..=center.x + dist).map(move |x| ChunkPos { x, z }))
.filter(move |&p| is_chunk_in_view_distance(center, p, distance))
}
pub fn is_chunk_in_view_distance(p0: ChunkPos, p1: ChunkPos, distance: u8) -> bool {
(p0.x as f64 - p1.x as f64).powi(2) + (p0.z as f64 - p1.z as f64).powi(2)
<= (distance as f64 + EXTRA_RADIUS as f64).powi(2)
}
pub(crate) fn aabb_from_bottom_and_size<T>(bottom: Vec3<T>, size: Vec3<T>) -> Aabb<T>
where
T: Float + 'static,
f64: AsPrimitive<T>,
{
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 + size.y,
bottom.z + size.z / 2.0.as_(),
),
};
debug_assert!(aabb.is_valid());
aabb
}
pub fn to_yaw_and_pitch(d: Vec3<f64>) -> (f64, f64) {
debug_assert!(d.is_normalized(), "the given vector should be normalized");
let yaw = f64::atan2(d.z, d.x).to_degrees() - 90.0;
let pitch = -(d.y).asin().to_degrees();
(yaw, pitch)
}
pub fn from_yaw_and_pitch(yaw: f64, pitch: f64) -> Vec3<f64> {
let yaw = (yaw + 90.0).to_radians();
let pitch = (-pitch).to_radians();
let xz_len = pitch.cos();
Vec3::new(yaw.cos() * xz_len, pitch.sin(), yaw.sin() * xz_len)
}
pub fn ray_box_intersect(ro: Vec3<f64>, rd: Vec3<f64>, bb: Aabb<f64>) -> Option<(f64, f64)> {
let mut near = -f64::INFINITY;
let mut far = f64::INFINITY;
for i in 0..3 {
let t0 = (bb.min[i] - ro[i]) / rd[i];
let t1 = (bb.max[i] - ro[i]) / rd[i];
near = near.max(t0.min(t1));
far = far.min(t0.max(t1));
}
if near <= far && far >= 0.0 {
Some((near.max(0.0), far))
} else {
None
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use rand::random;
use super::*;
#[test]
fn yaw_pitch_round_trip() {
for _ in 0..=100 {
let d = (Vec3::new(random(), random(), random()) * 2.0 - 1.0).normalized();
let (yaw, pitch) = to_yaw_and_pitch(d);
let d_new = from_yaw_and_pitch(yaw, pitch);
assert_relative_eq!(d, d_new, epsilon = f64::EPSILON * 100.0);
}
}
#[test]
fn ray_box_edge_cases() {
let bb = Aabb {
min: Vec3::new(0.0, 0.0, 0.0),
max: Vec3::new(1.0, 1.0, 1.0),
};
let ros = [
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(-0.5, 0.5, -0.5),
Vec3::new(0.5, 0.5, 0.5),
Vec3::new(0.0, 0.5, 0.0),
Vec3::new(0.0, 0.5, 0.5),
Vec3::new(-2.0, -2.0, -2.0),
];
let rds = [
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(-1.0, 0.0, 0.0),
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(0.0, -1.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(0.0, 0.0, -1.0),
];
assert!(rds.iter().all(|d| d.is_normalized()));
for ro in ros {
for rd in rds {
if let Some((near, far)) = ray_box_intersect(ro, rd, bb) {
assert!(near.is_finite());
assert!(far.is_finite());
assert!(near <= far);
assert!(near >= 0.0);
assert!(far >= 0.0);
}
}
}
}
}