use std::{cmp::Ordering::Less, collections::HashMap, fs::File, ops::Range, path::Path};
use crate::{Color, Dual, Error::*, Point, Polygon, Result, Shape};
#[derive(Debug)]
pub struct Model {
width: i32,
height: i32,
scale: f64,
shapes: Vec<Shape>,
lookup: HashMap<Point, Shape>,
}
impl Model {
pub fn new(width: i32, height: i32, scale: f64) -> Model {
Model {
width,
height,
scale,
shapes: Vec::new(),
lookup: HashMap::new(),
}
}
pub fn add(&mut self, shape: Shape) {
self.shapes.push(shape);
self.lookup.insert(shape.point(), shape);
}
pub fn add_multi(
&mut self,
indexes: Range<usize>,
edges: Range<usize>,
shape: Shape,
) -> Result<Range<usize>> {
let start = self.shapes.len();
for i in indexes {
for e in edges.clone() {
self.attach(i, e, shape)?;
}
}
let end = self.shapes.len();
Ok(start..end)
}
fn attach(&mut self, index: usize, edge: usize, shape: Shape) -> Result<()> {
let parent = self.shapes.get(index).ok_or(OutOfBounds {
index: index,
length: self.shapes.len(),
name: String::from("model shapes"),
})?;
let shape = parent.adjacent(shape.sides(), edge, shape.fill(), shape.stroke())?;
self.add(shape);
Ok(())
}
pub fn repeat(&mut self, indexes: Range<usize>) -> Result<()> {
let mut memo: HashMap<Point, i32> = HashMap::new();
let mut depth = 0;
loop {
self.repeat_r(indexes.clone(), Point::origin(), depth, &mut memo)?;
let w = self.width as f64 / 2.0 / self.scale;
let h = self.height as f64 / 2.0 / self.scale;
let tl = memo.keys().any(|p| p.x < -w && p.y < -h);
let tr = memo.keys().any(|p| p.x > w && p.y < -h);
let bl = memo.keys().any(|p| p.x < -w && p.y > h);
let br = memo.keys().any(|p| p.x > w && p.y > h);
if tl && tr && bl && br {
break;
}
depth += 1;
}
Ok(())
}
fn repeat_r(
&mut self,
indexes: Range<usize>,
point: Point,
depth: i32,
memo: &mut HashMap<Point, i32>,
) -> Result<()> {
if depth < 0 {
return Ok(());
}
let prev_depth = *memo.get(&point).unwrap_or(&-1);
if prev_depth >= depth {
return Ok(());
}
memo.insert(point, depth);
if prev_depth == -1 {
self.add_repeats(point);
}
let mut shapes = Vec::new();
for i in indexes.clone() {
let s = self.shapes.get(i).ok_or(OutOfBounds {
index: i,
length: self.shapes.len(),
name: String::from("model shapes"),
})?;
shapes.push(*s);
}
for s in shapes.iter() {
self.repeat_r(indexes.clone(), point + s.point(), depth - 1, memo)?;
}
Ok(())
}
fn add_repeats(&mut self, point: Point) {
for s in self.shapes.iter() {
let p = point + s.point();
if self.lookup.contains_key(&p) {
continue;
}
self.lookup.insert(p, s.clone_at(p));
}
}
fn dual(&self, fill: Color, stroke: Color) -> Result<Vec<Dual>> {
let mut vertexes: HashMap<Point, Vec<Shape>> = HashMap::new();
for s in self.lookup.values() {
let points = s.points(0.0)?;
for p in &points[0..points.len() - 1] {
if let Some(shapes) = vertexes.get_mut(p) {
shapes.push(*s);
} else {
vertexes.insert(*p, vec![*s]);
}
}
}
let mut duals: Vec<Dual> = Vec::new();
for (p, shapes) in vertexes.iter_mut() {
if shapes.len() < 3 {
continue;
}
let angle = |s: &Shape| (s.point().y - p.y).atan2(s.point().x - p.x);
shapes.sort_by(|a, b| angle(b).partial_cmp(&angle(a)).unwrap_or(Less));
let mut points = shapes.iter().map(|s| s.point()).collect::<Vec<Point>>();
points.push(*points.first().ok_or(OutOfBounds {
index: 0,
length: shapes.len(),
name: String::from("dual shapes"),
})?);
duals.push(Dual::new(points, fill, stroke));
}
Ok(duals)
}
pub fn render(
&self,
background: Color,
margin: f64,
line_width: f64,
show_labels: bool,
) -> Result<Render> {
let (surface, context) = self.render_init(background, line_width)?;
let shapes = self.lookup.values();
if show_labels {
for s in shapes.clone() {
s.render_edge_labels(&context, margin - 0.25)?;
}
}
for s in shapes.clone() {
s.render(&context, margin)?;
}
if show_labels {
for (i, s) in shapes.clone().enumerate() {
s.render_label(&context, &i.to_string())?;
}
}
Ok(Render(surface))
}
pub fn render_dual(
&self,
background: Color,
fill: Color,
stroke: Color,
margin: f64,
line_width: f64,
) -> Result<Render> {
let (surface, context) = self.render_init(background, line_width)?;
let shapes = self.dual(fill, stroke)?;
for s in shapes.clone() {
s.render(&context, margin)?;
}
Ok(Render(surface))
}
fn render_init(
&self,
background: Color,
line_width: f64,
) -> Result<(cairo::ImageSurface, cairo::Context)> {
let surface = cairo::ImageSurface::create(cairo::Format::Rgb24, self.width, self.height)?;
let context = cairo::Context::new(&surface)?;
let (red, green, blue) = background.rgb_unit_int();
context.set_line_cap(cairo::LineCap::Round);
context.set_line_join(cairo::LineJoin::Round);
context.set_line_width(line_width);
context.set_font_size(18.0 / self.scale);
context.translate(self.width as f64 / 2.0, self.height as f64 / 2.0);
context.scale(self.scale, self.scale);
context.set_source_rgb(red, green, blue);
context.paint()?;
Ok((surface, context))
}
}
pub struct Render(cairo::ImageSurface);
impl Render {
pub fn write_to_png<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut file = File::create(path)?;
self.0.write_to_png(&mut file)?;
Ok(())
}
}