use crate::{Real, Scalar, Unit, kelvin, supports_value_type_conversion};
use approx::AbsDiffEq;
use std::{
fmt,
fmt::Debug,
marker::PhantomData,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
};
pub trait TemperatureUnit: Unit + Copy + Debug + Eq + PartialEq + 'static {
fn convert_to_kelvin(degrees_in: f64) -> f64;
fn convert_from_kelvin(degrees_k: f64) -> f64;
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Temperature<Unit: TemperatureUnit> {
kelvin: Real,
#[cfg_attr(feature = "serde", serde(skip))]
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
phantom: PhantomData<Unit>,
}
impl<Unit: TemperatureUnit> Temperature<Unit> {
pub fn f64(self) -> f64 {
f64::from(self)
}
pub fn f32(self) -> f32 {
f32::from(self)
}
}
impl<Unit> fmt::Display for Temperature<Unit>
where
Unit: TemperatureUnit,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&Unit::convert_from_kelvin(self.kelvin.0), f)?;
write!(f, "{}", Unit::UNIT_SUFFIX)
}
}
impl<'a, UnitA, UnitB> From<&'a Temperature<UnitA>> for Temperature<UnitB>
where
UnitA: TemperatureUnit,
UnitB: TemperatureUnit,
{
fn from(v: &'a Temperature<UnitA>) -> Self {
Self {
kelvin: v.kelvin,
phantom: PhantomData,
}
}
}
impl<Unit> AbsDiffEq for Temperature<Unit>
where
Unit: TemperatureUnit,
{
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
f64::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
let a = Unit::convert_from_kelvin(self.kelvin.0);
let b = Unit::convert_from_kelvin(other.kelvin.0);
a.abs_diff_eq(&b, epsilon)
}
}
impl<UnitA, UnitB> Add<Temperature<UnitB>> for Temperature<UnitA>
where
UnitA: TemperatureUnit,
UnitB: TemperatureUnit,
{
type Output = Temperature<UnitA>;
fn add(self, rhs: Temperature<UnitB>) -> Self::Output {
Temperature::<UnitA>::from(&kelvin!(self.kelvin.0 + rhs.kelvin.0))
}
}
impl<UnitA, UnitB> AddAssign<Temperature<UnitB>> for Temperature<UnitA>
where
UnitA: TemperatureUnit,
UnitB: TemperatureUnit,
{
fn add_assign(&mut self, rhs: Temperature<UnitB>) {
self.kelvin += rhs.kelvin;
}
}
impl<UnitA, UnitB> Sub<Temperature<UnitB>> for Temperature<UnitA>
where
UnitA: TemperatureUnit,
UnitB: TemperatureUnit,
{
type Output = Temperature<UnitA>;
fn sub(self, rhs: Temperature<UnitB>) -> Self::Output {
Temperature::<UnitA>::from(&kelvin!(self.kelvin.0 - rhs.kelvin.0))
}
}
impl<UnitA, UnitB> SubAssign<Temperature<UnitB>> for Temperature<UnitA>
where
UnitA: TemperatureUnit,
UnitB: TemperatureUnit,
{
fn sub_assign(&mut self, rhs: Temperature<UnitB>) {
self.kelvin -= rhs.kelvin;
}
}
impl<Unit> Mul<Scalar> for Temperature<Unit>
where
Unit: TemperatureUnit,
{
type Output = Temperature<Unit>;
fn mul(self, other: Scalar) -> Self {
Self {
kelvin: self.kelvin * other.into_real(),
phantom: PhantomData,
}
}
}
impl<Unit> Mul<Temperature<Unit>> for Scalar
where
Unit: TemperatureUnit,
{
type Output = Temperature<Unit>;
fn mul(self, other: Temperature<Unit>) -> Self::Output {
Self::Output {
kelvin: other.kelvin * self.into_real(),
phantom: PhantomData,
}
}
}
impl<Unit> MulAssign<Scalar> for Temperature<Unit>
where
Unit: TemperatureUnit,
{
fn mul_assign(&mut self, other: Scalar) {
self.kelvin *= other.into_real();
}
}
impl<Unit> Div<Scalar> for Temperature<Unit>
where
Unit: TemperatureUnit,
{
type Output = Temperature<Unit>;
fn div(self, other: Scalar) -> Self {
Self {
kelvin: self.kelvin / other.into_real(),
phantom: PhantomData,
}
}
}
impl<Unit> DivAssign<Scalar> for Temperature<Unit>
where
Unit: TemperatureUnit,
{
fn div_assign(&mut self, other: Scalar) {
self.kelvin /= other.into_real();
}
}
macro_rules! impl_temperature_unit_for_numeric_type {
($Num:ty) => {
impl<Unit> From<$Num> for Temperature<Unit>
where
Unit: TemperatureUnit,
{
fn from(v: $Num) -> Self {
Self {
kelvin: Real::new(Unit::convert_to_kelvin(v as f64)),
phantom: PhantomData,
}
}
}
impl<Unit> From<&$Num> for Temperature<Unit>
where
Unit: TemperatureUnit,
{
fn from(v: &$Num) -> Self {
Self {
kelvin: Real::new(Unit::convert_to_kelvin(*v as f64)),
phantom: PhantomData,
}
}
}
impl<Unit> From<Temperature<Unit>> for $Num
where
Unit: TemperatureUnit,
{
fn from(v: Temperature<Unit>) -> $Num {
Unit::convert_from_kelvin(v.kelvin.0) as $Num
}
}
};
}
supports_value_type_conversion!(impl_temperature_unit_for_numeric_type);
#[cfg(test)]
mod test {
use crate::{celsius, fahrenheit, kelvin, rankine};
#[test]
fn test_meters_to_feet() {
let f = fahrenheit!(100);
println!("f: {f}");
println!("c: {}", celsius!(f));
println!("r: {}", rankine!(f));
println!("k: {}", kelvin!(f));
}
}