126 lines
3.7 KiB
Rust
126 lines
3.7 KiB
Rust
#![feature(generic_const_exprs)]
|
|
#![feature(let_chains)]
|
|
|
|
use std::fs::File;
|
|
use std::path::PathBuf;
|
|
use anyhow::Context;
|
|
use crate::cli::cli::OutputFormat;
|
|
use crate::system::model::HistoryLine;
|
|
use crate::system::spaces::square_grid::Grid2D;
|
|
use clap::Parser;
|
|
use colorous::Color;
|
|
use itertools::Itertools;
|
|
use num_traits::real::Real;
|
|
use num_traits::Signed;
|
|
use serde::de::DeserializeOwned;
|
|
use serde::Deserialize;
|
|
use svg::Node;
|
|
use svg::node::element::{Circle, Polygon, Rectangle};
|
|
use crate::{RenderCli, Space};
|
|
use crate::system::{GriddedPosition, Position};
|
|
use crate::system::spaces::continuous_2d::P2;
|
|
use crate::system::spaces::hexagonal::HexPosition;
|
|
use crate::tools::read;
|
|
|
|
#[derive(Debug, Parser)]
|
|
struct Args {
|
|
format: OutputFormat,
|
|
path: PathBuf,
|
|
output: PathBuf,
|
|
}
|
|
|
|
trait ToSvg {
|
|
fn to_svg(&self, colour: Color) -> Box<dyn Node>;
|
|
}
|
|
|
|
impl ToSvg for Grid2D {
|
|
fn to_svg(&self, colour: Color) -> Box<dyn Node> {
|
|
Box::new(Rectangle::new()
|
|
.set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b))
|
|
.set("width", 1)
|
|
.set("height", 1)
|
|
.set("x", self.x)
|
|
.set("y", self.y))
|
|
}
|
|
}
|
|
|
|
impl ToSvg for P2 {
|
|
fn to_svg(&self, colour: Color) -> Box<dyn Node> {
|
|
Box::new(Circle::new()
|
|
.set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b))
|
|
.set("r", 1)
|
|
.set("cx", self.x)
|
|
.set("cy", self.y))
|
|
}
|
|
}
|
|
|
|
impl ToSvg for HexPosition {
|
|
fn to_svg(&self, colour: Color) -> Box<dyn Node> {
|
|
let points = [
|
|
[25.045, 128.0], [256.0, 0.0], [486.955, 128.0], [486.955, 384.0], [256.0, 512.0], [25.045, 384.0]
|
|
];
|
|
|
|
let a = 256.0;
|
|
// let a = 400.0;
|
|
let b = points.map(|x| [
|
|
(x[0] / a),
|
|
(x[1] / a)]
|
|
);
|
|
|
|
let c = b.map(|p| format!("{},{}", p[0], p[1])).join(" ");
|
|
|
|
let cartesian = self.to_cartesian();
|
|
Box::new(Polygon::new()
|
|
.set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b))
|
|
.set("points", c)
|
|
.set("transform", format!("translate({}, {})", cartesian[0], cartesian[1])))
|
|
}
|
|
}
|
|
|
|
pub fn compute_max_size<P: Position>(positions: &[P]) -> f32 {
|
|
let mut positions = positions.iter().map(P::to_cartesian);
|
|
let mut maximums: Vec<f32> = positions.next().unwrap();
|
|
|
|
for cartesian in positions {
|
|
for i in 0..maximums.len() {
|
|
maximums[i] = maximums[i].abs().max(cartesian[i].abs());
|
|
}
|
|
}
|
|
|
|
maximums.into_iter()
|
|
.fold(0f32, |r, c| r.abs().max(c.abs()))
|
|
}
|
|
|
|
fn render<P: Position>(args: &RenderCli) where P: DeserializeOwned + ToSvg {
|
|
let positions = read::<P>(&args.path, args.format);
|
|
let max_size = compute_max_size(&positions) + 10.0;
|
|
|
|
let mut svg = svg::Document::new()
|
|
.set("width", args.image_size)
|
|
.set("height", args.image_size)
|
|
.set("viewBox", format!("{} {} {} {}", -max_size, -max_size, max_size * 2.0, max_size * 2.0));
|
|
|
|
svg.append(Rectangle::new()
|
|
.set("fill", "white")
|
|
.set("width", max_size * 2.0)
|
|
.set("height", max_size * 2.0)
|
|
.set("x", -max_size)
|
|
.set("y", -max_size)
|
|
);
|
|
|
|
for (n, position) in positions.iter().enumerate() {
|
|
let colour = if args.colour { colorous::VIRIDIS.eval_rational(n, positions.len()) } else { Color::default() };
|
|
svg.append(position.to_svg(colour));
|
|
}
|
|
|
|
svg::write(File::create(&args.output).unwrap(), &svg).unwrap();
|
|
}
|
|
|
|
pub(crate) fn main(args: &RenderCli) {
|
|
match args.space {
|
|
Space::Grid2D => render::<Grid2D>(args),
|
|
Space::Continuous2D => render::<P2>(args),
|
|
Space::Hex => render::<HexPosition>(args),
|
|
}
|
|
}
|