use std::convert::TryFrom;
use std::ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Sub, SubAssign};
use crate::math::{Point3, Vec3};
#[derive(Copy, Clone, Default, Debug, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
}
impl Color {
pub const BLACK: Color = Color {
r: 0.0,
g: 0.0,
b: 0.0,
};
pub const WHITE: Color = Color {
r: 1.0,
g: 1.0,
b: 1.0,
};
pub fn new(r: f32, g: f32, b: f32) -> Self {
Color { r, g, b }
}
pub fn splat(value: f32) -> Self {
Color {
r: value,
g: value,
b: value,
}
}
pub fn srgb_to_linear(&self) -> Self {
fn convert(value: f32) -> f32 {
if value <= 0.0031308 {
value * 12.92
} else {
1.055 * value.powf(1.0 / 2.4) - 0.055
}
}
Color::new(convert(self.r), convert(self.g), convert(self.b))
}
#[cfg(test)]
fn abs(&self) -> f32 {
self.r.abs() + self.g.abs() + self.b.abs()
}
}
impl Add<Color> for Color {
type Output = Color;
fn add(self, rhs: Color) -> Self::Output {
Color {
r: self.r + rhs.r,
g: self.g + rhs.g,
b: self.b + rhs.b,
}
}
}
impl AddAssign<Color> for Color {
fn add_assign(&mut self, rhs: Color) {
*self = *self + rhs
}
}
impl Sub<Color> for Color {
type Output = Color;
fn sub(self, rhs: Color) -> Self::Output {
Color {
r: self.r - rhs.r,
g: self.g - rhs.g,
b: self.b - rhs.b,
}
}
}
impl SubAssign<Color> for Color {
fn sub_assign(&mut self, rhs: Color) {
*self = *self - rhs
}
}
impl Mul<Color> for Color {
type Output = Color;
fn mul(self, rhs: Color) -> Self::Output {
Color {
r: self.r * rhs.r,
g: self.g * rhs.g,
b: self.b * rhs.b,
}
}
}
impl MulAssign<Color> for Color {
fn mul_assign(&mut self, rhs: Color) {
*self = *self * rhs
}
}
impl Mul<f32> for Color {
type Output = Color;
fn mul(self, rhs: f32) -> Self::Output {
Color {
r: self.r * rhs,
g: self.g * rhs,
b: self.b * rhs,
}
}
}
impl MulAssign<f32> for Color {
fn mul_assign(&mut self, rhs: f32) {
*self = *self * rhs;
}
}
impl Mul<Color> for f32 {
type Output = Color;
fn mul(self, rhs: Color) -> Color {
rhs * self
}
}
impl Div<Color> for Color {
type Output = Color;
fn div(self, rhs: Color) -> Self::Output {
Color {
r: self.r / rhs.r,
g: self.g / rhs.g,
b: self.b / rhs.b,
}
}
}
impl DivAssign<Color> for Color {
fn div_assign(&mut self, rhs: Color) {
*self = *self / rhs
}
}
impl Div<f32> for Color {
type Output = Color;
fn div(self, rhs: f32) -> Self::Output {
Color {
r: self.r / rhs,
g: self.g / rhs,
b: self.b / rhs,
}
}
}
impl DivAssign<f32> for Color {
fn div_assign(&mut self, rhs: f32) {
*self = *self / rhs;
}
}
impl From<[f32; 3]> for Color {
fn from(s: [f32; 3]) -> Self {
Color {
r: s[0],
g: s[1],
b: s[2],
}
}
}
impl From<(f32, f32, f32)> for Color {
fn from(t: (f32, f32, f32)) -> Self {
Color {
r: t.0,
g: t.1,
b: t.2,
}
}
}
impl From<Vec3> for Color {
fn from(v: Vec3) -> Self {
Color {
r: v.x as f32,
g: v.y as f32,
b: v.z as f32,
}
}
}
impl From<Point3> for Color {
fn from(p: Point3) -> Self {
Color {
r: p.x as f32,
g: p.y as f32,
b: p.z as f32,
}
}
}
impl TryFrom<Vec<f32>> for Color {
type Error = &'static str;
fn try_from(v: Vec<f32>) -> Result<Self, Self::Error> {
if v.len() != 3 {
Err("Color can only be build from a vector of length 3.")
} else {
Ok(Color {
r: v[0],
g: v[1],
b: v[2],
})
}
}
}
impl TryFrom<&[f32]> for Color {
type Error = &'static str;
fn try_from(s: &[f32]) -> Result<Self, Self::Error> {
if s.len() != 3 {
Err("Color can only be build from a slice of length 3.")
} else {
Ok(Color {
r: s[0],
g: s[1],
b: s[2],
})
}
}
}
impl Index<usize> for Color {
type Output = f32;
fn index(&self, index: usize) -> &Self::Output {
match index {
0 => &self.r,
1 => &self.g,
2 => &self.b,
_ => panic!(
"index out of bounds: the len is 3 but the index is {}",
index
),
}
}
}
impl IndexMut<usize> for Color {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
match index {
0 => &mut self.r,
1 => &mut self.g,
2 => &mut self.b,
_ => panic!(
"index out of bounds: the len is 3 but the index is {}",
index
),
}
}
}
#[cfg(test)]
mod tests {
use std::convert::TryFrom;
use assert_approx_eq::assert_approx_eq;
use crate::math::{Point3, Vec3};
use crate::util::EPSILON_F32;
use super::Color;
#[test]
fn color_arithmetic_operations() {
assert_approx_eq!(
Color::new(2.0, 1.0, 0.0) + Color::splat(1.0),
Color::new(3.0, 2.0, 1.0),
EPSILON_F32
);
assert_approx_eq!(
Color::new(5.72, 2.5, 8.824) + Color::new(8.7, 5.987, 0.12),
Color::new(14.42, 8.487, 8.944),
EPSILON_F32
);
let mut color1 = Color::new(7.0, 2.5, 3.2);
color1 += Color::new(1.2, 9.23, 6.2);
assert_approx_eq!(color1, Color::new(8.2, 11.73, 9.4), EPSILON_F32);
assert_approx_eq!(
Color::new(2.0, 1.0, 0.0) - Color::splat(1.0),
Color::new(1.0, 0.0, -1.0),
EPSILON_F32
);
assert_approx_eq!(
Color::new(5.72, 2.5, 8.824) - Color::new(8.7, 5.987, 0.12),
Color::new(-2.98, -3.487, 8.704),
EPSILON_F32
);
let mut color2 = Color::new(7.0, 2.5, 3.2);
color2 -= Color::new(1.2, 9.23, 6.2);
assert_approx_eq!(color2, Color::new(5.8, -6.73, -3.0), EPSILON_F32);
assert_approx_eq!(
Color::new(2.0, 1.0, 0.0) * 2.0,
Color::new(4.0, 2.0, 0.0),
EPSILON_F32
);
assert_approx_eq!(
Color::new(2.0, 1.0, 0.0) * 2.0,
2.0 * Color::new(2.0, 1.0, 0.0),
EPSILON_F32
);
assert_approx_eq!(
Color::new(8.7, 5.987, 0.12) * Color::new(0.5, 1.0, 2.0),
Color::new(4.35, 5.987, 0.24),
EPSILON_F32
);
let mut color3 = Color::new(7.0, 2.5, 3.2);
color3 *= -2.0;
assert_approx_eq!(color3, Color::new(-14.0, -5.0, -6.4), EPSILON_F32);
color3 *= Color::new(0.5, -2.0, 3.0);
assert_approx_eq!(color3, Color::new(-7.0, 10.0, -19.2), EPSILON_F32);
assert_approx_eq!(
Color::new(2.0, 1.0, 0.0) / 2.0,
Color::new(1.0, 0.5, 0.0),
EPSILON_F32
);
assert_approx_eq!(
Color::new(8.7, 5.987, 0.12) / Color::new(0.5, 1.0, 2.0),
Color::new(17.4, 5.987, 0.06),
EPSILON_F32
);
let mut color4 = Color::new(7.0, 2.5, 3.6);
color4 /= -2.0;
assert_approx_eq!(color4, Color::new(-3.5, -1.25, -1.8), EPSILON_F32);
color4 /= Color::new(0.5, -2.0, 3.0);
assert_approx_eq!(color4, Color::new(-7.0, 0.625, -0.6), EPSILON_F32);
}
#[test]
fn color_creation() {
let color = Color::splat(0.5);
assert_approx_eq!(color.r, 0.5, EPSILON_F32);
assert_approx_eq!(color.g, 0.5, EPSILON_F32);
assert_approx_eq!(color.b, 0.5, EPSILON_F32);
assert_approx_eq!(
Color::new(1.0, 3.98, -4.2),
Color::from([1.0, 3.98, -4.2]),
EPSILON_F32
);
assert_approx_eq!(
Color::new(1.0, 3.98, -4.2),
Color::from((1.0, 3.98, -4.2)),
EPSILON_F32
);
assert_approx_eq!(
Color::new(1.0, 3.98, -4.2),
Color::from(Vec3::new(1.0, 3.98, -4.2)),
EPSILON_F32
);
assert_approx_eq!(
Color::new(1.0, 3.98, -4.2),
Color::from(Point3::new(1.0, 3.98, -4.2)),
EPSILON_F32
);
assert_approx_eq!(
Color::new(1.0, 3.98, -4.2),
Color::try_from(vec![1.0, 3.98, -4.2]).unwrap(),
EPSILON_F32
);
assert!(Color::try_from(vec![1.0, 3.98]).is_err());
assert!(Color::try_from(vec![1.0, 3.98, -4.2, 93.12]).is_err());
assert_approx_eq!(
Color::new(1.0, 3.98, -4.2),
Color::try_from([1.0, 3.98, -4.2].as_ref()).unwrap(),
EPSILON_F32
);
assert!(Color::try_from([1.0, 3.98].as_ref()).is_err());
assert!(Color::try_from([1.0, 3.98, -4.2, 93.12].as_ref()).is_err());
}
#[test]
fn color_index() {
let mut color = Color::new(1.0, 3.98, -4.2);
assert_approx_eq!(color.r, color[0]);
assert_approx_eq!(color.g, color[1]);
assert_approx_eq!(color.b, color[2]);
color[0] = 8.7;
color[1] = 5.987;
color[2] = 0.12;
assert_approx_eq!(color, Color::new(8.7, 5.987, 0.12));
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn color_index_panic() {
let color = Color::new(1.0, 3.98, -4.2);
let _x = color[4];
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn color_index_mut_panic() {
let mut color = Color::new(1.0, 3.98, -4.2);
color[4] = 93.12;
}
}