use crate::{
canvas::Paint,
utils::{check_zoom, mean, RsilleErr, MIN_DIFFERENCE},
Canvas,
};
use crate::color::Color;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Object3D {
origin_vertices: Vec<Point3D>,
zoomed_vertices: Option<Vec<Point3D>>,
center: Point3D,
sides: HashMap<(usize, usize), Color>,
}
impl Object3D {
pub fn new() -> Self {
Self {
origin_vertices: Vec::new(),
zoomed_vertices: None,
center: Point3D::new(0.0, 0.0, 0.0),
sides: HashMap::new(),
}
}
pub fn vertices(&self) -> Vec<(f64, f64, f64)> {
self.origin_vertices.iter().map(|p| p.get()).collect()
}
pub fn add_points(&mut self, points: &[(f64, f64, f64)]) {
for p in points {
self.origin_vertices.push(Point3D::from(*p));
}
self.calc_center();
}
pub fn sides(&self) -> Vec<(usize, usize)> {
self.sides.keys().cloned().collect()
}
pub fn add_sides(&mut self, sides: &[(usize, usize)]) -> Result<(), RsilleErr> {
let vn = self.origin_vertices.len();
for side in sides {
if vn <= side.0 || vn <= side.1 {
return Err(RsilleErr::new("wrong add sides!".to_string()));
}
self.sides.insert(*side, Color::Reset);
}
Ok(())
}
pub fn add_sides_colorful(
&mut self,
sides: &[((usize, usize), Color)],
) -> Result<(), RsilleErr> {
let vn = self.origin_vertices.len();
for (side, color) in sides {
if vn <= side.0 || vn <= side.1 {
return Err(RsilleErr::new("wrong add sides!".to_string()));
}
self.sides.insert(*side, *color);
}
Ok(())
}
pub fn set_side_colorful(&mut self, side: (usize, usize), color: Color) {
if self.sides.contains_key(&side) {
self.sides.insert(side, color);
}
}
pub fn rotate(&mut self, angle: (f64, f64, f64)) {
for p in &mut self.origin_vertices {
p.rotate(angle);
}
}
pub fn rotate_new(&self, angle: (f64, f64, f64)) -> Self {
let mut obj = self.clone();
obj.rotate(angle);
obj
}
pub fn zoom(&mut self, factor: f64) {
check_zoom(factor);
let mut vertices = self.origin_vertices.clone();
vertices
.iter_mut()
.for_each(|v| v.zoom(self.center, factor));
self.zoomed_vertices = Some(vertices);
}
pub fn zoom_new(&self, factor: f64) -> Self {
check_zoom(factor);
let mut points: Vec<Point3D> = self.origin_vertices.clone();
points.iter_mut().for_each(|v| v.zoom(self.center, factor));
Self {
origin_vertices: points,
zoomed_vertices: None,
sides: self.sides.clone(),
center: self.center,
}
}
pub fn map<F>(&mut self, f: F)
where
F: Fn(f64, f64, f64) -> (f64, f64, f64),
{
for p in &mut self.origin_vertices {
let (x, y, z) = f(p.x, p.y, p.z);
p.x = x;
p.y = y;
p.z = z;
}
}
fn calc_center(&mut self) {
let (mut xs, mut ys, mut zs) = (Vec::new(), Vec::new(), Vec::new());
for p in &self.origin_vertices {
xs.push(p.x);
ys.push(p.y);
zs.push(p.z);
}
let (mut mx, mut my, mut mz) = (mean(&xs), mean(&ys), mean(&zs));
if (mx - self.center.x) < MIN_DIFFERENCE {
mx = self.center.x;
}
if (my - self.center.y) < MIN_DIFFERENCE {
my = self.center.y;
}
if (mz - self.center.z) < MIN_DIFFERENCE {
mz = self.center.z;
}
self.center = Point3D::new(mx, my, mz);
}
}
impl Paint for Object3D {
fn paint<T>(&self, canvas: &mut Canvas, x: T, y: T) -> Result<(), RsilleErr>
where
T: Into<f64>,
{
let (x, y) = (x.into(), y.into());
let points = if let Some(p) = &self.zoomed_vertices {
p
} else {
&self.origin_vertices
};
for (side, color) in &self.sides {
let (v1, v2) = (points[side.0], points[side.1]);
let xy1 = (x + v1.x, y + v1.z);
let xy2 = (x + v2.x, y + v2.z);
canvas.line_colorful(xy1, xy2, *color);
}
Ok(())
}
}
impl Object3D {
pub fn cube<T>(side_len: T) -> Object3D
where
T: Into<f64>,
{
let side_len = side_len.into();
let mut object = Object3D::new();
#[rustfmt::skip]
let a = [
(-1, -1, -1),
(-1, -1, 1),
(-1, 1, -1),
( 1, -1, -1),
(-1, 1, 1),
( 1, -1, 1),
( 1, 1, -1),
( 1, 1, 1),
];
let mut points = Vec::new();
for i in a {
let x = side_len / 2.0 * i.0 as f64;
let y = side_len / 2.0 * i.1 as f64;
let z = side_len / 2.0 * i.2 as f64;
points.push((x, y, z));
}
object.add_points(&points);
object
.add_sides(&[
(0, 1),
(1, 4),
(4, 2),
(2, 0),
(3, 5),
(5, 7),
(7, 6),
(6, 3),
(1, 5),
(4, 7),
(2, 6),
(0, 3),
])
.unwrap();
object
}
}
#[derive(Debug, Clone, Copy)]
struct Point3D {
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) z: f64,
}
#[allow(unused)]
impl Point3D {
fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
fn from(xyz: (f64, f64, f64)) -> Self {
Self {
x: xyz.0,
y: xyz.1,
z: xyz.2,
}
}
fn get(&self) -> (f64, f64, f64) {
(self.x, self.y, self.z)
}
fn rotate(&mut self, angle: (f64, f64, f64)) {
self.rotate_x(angle.0);
self.rotate_y(angle.1);
self.rotate_z(angle.2);
}
fn rotate_new(&self, angle: (f64, f64, f64)) -> Self {
let mut point = *self;
point.rotate(angle);
point
}
fn zoom(&mut self, center: Self, factor: f64) {
check_zoom(factor);
self.x = (self.x - center.x) * factor;
self.y = (self.y - center.y) * factor;
self.z = (self.z - center.z) * factor;
}
fn zoom_new(&self, center: Self, factor: f64) -> Point3D {
check_zoom(factor);
let dx = (self.x - center.x) * factor;
let dy = (self.y - center.y) * factor;
let dz = (self.z - center.y) * factor;
Self {
x: dx,
y: dy,
z: dz,
}
}
fn rotate_x(&mut self, angle: f64) {
let (s, c) = angle.to_radians().sin_cos();
let (y, z) = (self.y, self.z);
self.y = y * c - z * s;
self.z = y * s + z * c;
}
fn rotate_y(&mut self, angle: f64) {
let (s, c) = angle.to_radians().sin_cos();
let (x, z) = (self.x, self.z);
self.x = x * c + z * s;
self.z = -x * s + z * c;
}
fn rotate_z(&mut self, anlge: f64) {
let (s, c) = anlge.to_radians().sin_cos();
let (x, y) = (self.x, self.y);
self.x = x * c - y * s;
self.y = x * s + y * c;
}
}