use std::{ error::Error, f32::consts::{PI, TAU}, fmt::{Debug, Display}, }; use bevy::math::Vec3A; use rand::{rngs::StdRng, Rng}; #[derive(Debug, Copy, Clone)] pub enum CartesianError { InvalidAlpha(f32), } impl Error for CartesianError { fn source(&self) -> Option<&(dyn Error + 'static)> { None } fn description(&self) -> &str { "description() is deprecated; use Display" } fn cause(&self) -> Option<&dyn Error> { self.source() } } impl Display for CartesianError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CartesianError::InvalidAlpha(alpha) => { f.write_fmt(format_args!("Alpha value must be [0..PI], was {}", alpha)) } } } } pub fn cartesian_coordinates( alpha: f32, mut beta: f32, radius: f32, ) -> Result<Vec3A, CartesianError> { if alpha < 0.0 || alpha > PI { return Err(CartesianError::InvalidAlpha(alpha)); } if beta < 0.0 { while beta < 0.0 { beta += PI; } } else { beta = beta.repeat(TAU); } let sin_alpha = f32::sin(alpha); Ok(Vec3A::new( sin_alpha * f32::cos(beta) * radius, f32::cos(alpha) * radius, sin_alpha * f32::sin(beta) * radius, )) } pub fn random_point_in_sphere(rng: &mut StdRng, radius: f32) -> Vec3A { // https://karthikkaranth.me/blog/generating-random-points-in-a-sphere/#better-choice-of-spherical-coordinates let u = rng.gen_range(0.0..1.0); let v = rng.gen_range(0.0..1.0); let theta = u * TAU; let phi = f32::acos(2.0 * v - 1.0); let r = f32::cbrt(rng.gen_range(0.0..radius)); let sin_theta = f32::sin(theta); let cos_theta = f32::cos(theta); let sin_phi = f32::sin(phi); let cos_phi = f32::cos(phi); Vec3A::new( r * sin_phi * cos_theta, r * sin_phi * sin_theta, r * cos_phi, ) } pub fn mix_values(a: f32, b: f32, weight_b: f32) -> f32 { (b * weight_b) + (a * (1.0 - weight_b)) } pub trait RepeatNum { fn repeat(self, length: Self) -> Self; } impl RepeatNum for f32 { fn repeat(self, length: f32) -> f32 { f32::clamp(self - (self / length).floor() * length, 0.0, length) } }