use serde::Serialize;
use crate::{
generator::{geometry, Complex, EvaluationError},
script::figure::{Figure, LineDefinition, PointDefinition},
};
pub struct Blueprint {
pub points: Vec<RenderedPoint>,
pub lines: Vec<RenderedLine>,
}
#[derive(Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum Rendered {
Point(RenderedPoint),
Line(RenderedLine),
}
#[derive(Debug, Serialize)]
pub struct RenderedPoint {
pub label: String,
pub position: Complex,
}
#[derive(Serialize)]
pub struct RenderedLine {
pub label: String,
pub points: (Complex, Complex),
}
fn evaluate_line(line: &LineDefinition, points: &[Complex]) -> Result<Complex, EvaluationError> {
Ok(match line {
LineDefinition::TwoPoints(i1, i2) => {
geometry::get_line(evaluate_point(i1, points)?, evaluate_point(i2, points)?)
}
})
}
fn evaluate_point(
definition: &PointDefinition,
points: &[Complex],
) -> Result<Complex, EvaluationError> {
Ok(match definition {
PointDefinition::Indexed(gen_index) => points[*gen_index],
PointDefinition::Crossing(l1, l2) => {
let l1 = evaluate_line(l1, points)?;
let l2 = evaluate_line(l2, points)?;
geometry::get_crossing(l1, l2)?
}
})
}
fn get_line_ends(figure: &Figure, ln_c: Complex) -> (Complex, Complex) {
#[allow(clippy::cast_precision_loss)]
let width = figure.canvas_size.0 as f64;
#[allow(clippy::cast_precision_loss)]
let height = figure.canvas_size.1 as f64;
let intersections = [
geometry::get_crossing(
ln_c,
geometry::get_line(Complex::new(0.0, height), Complex::new(1.0, height)),
),
geometry::get_crossing(
ln_c,
geometry::get_line(Complex::new(0.0, 0.0), Complex::new(0.0, 1.0)),
),
geometry::get_crossing(
ln_c,
geometry::get_line(Complex::new(width, 0.0), Complex::new(width, 1.0)),
),
geometry::get_crossing(
ln_c,
geometry::get_line(Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)),
),
];
let a = ln_c.real;
#[allow(clippy::cast_precision_loss)]
if a < 0f64 {
let i1 = intersections[0].as_ref().map_or_else(
|_| intersections[1].as_ref().unwrap(),
|x| {
if (x.real > 0f64 && x.real < width) || intersections[1].is_err() {
x
} else {
intersections[1].as_ref().unwrap()
}
},
);
let i2 = intersections[3].as_ref().map_or_else(
|_| intersections[2].as_ref().unwrap(),
|x| {
if (x.real > 0f64 && x.real < width) || intersections[2].is_err() {
x
} else {
intersections[2].as_ref().unwrap()
}
},
);
(*i1, *i2)
} else {
let i1 = intersections[3].as_ref().map_or_else(
|_| intersections[1].as_ref().unwrap(),
|x| {
if (x.real > 0f64 && x.real < width) || intersections[1].is_err() {
x
} else {
intersections[1].as_ref().unwrap()
}
},
);
let i2 = intersections[0].as_ref().map_or_else(
|_| intersections[2].as_ref().unwrap(),
|x| {
if (x.real > 0f64 && x.real < width) || intersections[2].is_err() {
x
} else {
intersections[2].as_ref().unwrap()
}
},
);
(*i1, *i2)
}
}
pub fn project(
figure: &Figure,
generated_points: &[Complex],
) -> Result<Vec<Rendered>, EvaluationError> {
let points: Vec<Complex> = figure
.points
.iter()
.map(|pt| evaluate_point(&pt.definition, generated_points))
.collect::<Result<Vec<Complex>, EvaluationError>>()?;
#[allow(clippy::cast_precision_loss)]
let size1 = Complex::new(figure.canvas_size.0 as f64, figure.canvas_size.1 as f64);
let size09 = size1 * 0.9;
let size005 = size1 * 0.05;
let mut offset = points.get(0).copied().unwrap_or_default();
for x in &points {
if x.real < offset.real {
offset.real = x.real;
}
if x.imaginary < offset.imaginary {
offset.imaginary = x.imaginary;
}
}
let points: Vec<Complex> = points.into_iter().map(|x| x - offset).collect();
let mut furthest = points.get(0).copied().unwrap_or_default();
for x in &points {
if x.real > furthest.real {
furthest.real = x.real;
}
if x.imaginary > furthest.imaginary {
furthest.imaginary = x.imaginary;
}
}
let scale = f64::min(
size09.real / furthest.real,
size09.imaginary / furthest.imaginary,
);
let points: Vec<Complex> = points.into_iter().map(|x| x * scale + size005).collect();
let mut blueprint_points = Vec::new();
for (i, pt) in points.iter().enumerate() {
blueprint_points.push(RenderedPoint {
label: figure.points[i].label.clone(),
position: *pt,
});
}
let mut blueprint_lines = Vec::new();
for ln in &figure.lines {
let ln_c = evaluate_line(&ln.definition, generated_points)?;
blueprint_lines.push(RenderedLine {
label: ln.label.clone(),
points: get_line_ends(figure, ln_c),
});
}
Ok(blueprint_points
.into_iter()
.map(Rendered::Point)
.chain(blueprint_lines.into_iter().map(Rendered::Line))
.collect())
}