8 Commits
Author SHA1 Message Date
joshuacoles 192017d14e Add a README
continuous-integration/drone/push Build is failing
2023-03-20 15:15:50 +00:00
joshuacoles 10644adb54 Cleanup and fix accidental bevy imports 2023-03-20 15:13:45 +00:00
joshuacoles ded1b49068 Delete old clib items 2023-03-20 15:04:56 +00:00
joshuacoles 3ac782fb38 Delete unneeded targets and dependencies 2023-03-20 15:04:29 +00:00
joshuacoles f4962e060b Re-add image size determination for easier viewing of the raw SVG 2023-03-19 18:52:56 +00:00
joshuacoles 27529d38eb Nalg support 2023-03-19 10:26:33 +00:00
joshuacoles 9fe0337c9d Hex work!
continuous-integration/drone/push Build is failing
2023-03-18 19:52:33 +00:00
joshuacoles 596b2a509f Refactor Position thing (again, we love hitting compiler bugs), and add P2 rendering 2023-03-18 18:22:41 +00:00
21 changed files with 282 additions and 2829 deletions
Generated
+19 -2607
View File
File diff suppressed because it is too large Load Diff
+2 -11
View File
@@ -5,19 +5,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "dla"
crate-type = ["staticlib"]
path = "src/clib.rs"
[[bin]] [[bin]]
name = "model" name = "model"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
name = "ui"
path = "src/ui.rs"
[[bin]] [[bin]]
name = "tools" name = "tools"
path = "src/tools_cli.rs" path = "src/tools_cli.rs"
@@ -36,7 +27,6 @@ opt-level = 3
[dependencies] [dependencies]
clap = { version = "4.1.8", features = ["derive"] } clap = { version = "4.1.8", features = ["derive"] }
bevy = { version = "0.10.0" }
nd_array = "0.1.0" nd_array = "0.1.0"
num-integer = "0.1.45" num-integer = "0.1.45"
rand = { version = "0.8.5", features = ["default", "small_rng"] } rand = { version = "0.8.5", features = ["default", "small_rng"] }
@@ -44,7 +34,7 @@ csv = "1.1"
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93" serde_json = "1.0.93"
kd-tree = { version = "0.5.1", features = ["nalgebra"] } kd-tree = { version = "0.5.1", features = ["nalgebra"] }
nalgebra = "0.32.2" nalgebra = { version = "0.32.2", features = ["serde-serialize"] }
kiddo = "0.2.5" kiddo = "0.2.5"
anyhow = "1.0.69" anyhow = "1.0.69"
itertools = "0.10.5" itertools = "0.10.5"
@@ -65,6 +55,7 @@ rayon = "1.7.0"
walkdir = "2.3.3" walkdir = "2.3.3"
parquet = { version = "35.0.0", features = ["serde"] } parquet = { version = "35.0.0", features = ["serde"] }
colorous = "1.0.10" colorous = "1.0.10"
num-traits = "0.2.15"
[build-dependencies] [build-dependencies]
cbindgen = "0.24.3" cbindgen = "0.24.3"
+8
View File
@@ -0,0 +1,8 @@
# DLA Generic Model
A generic pluggable model for diffusion limited aggregation. Produces two executables,
- `./target/release/model`, built from `./src/main.rs`
- `./target/release/tools`, built from `./src/tools_cli.rs`
Build with `cargo build --release`. Requires a working rust installation.
-16
View File
@@ -1,16 +0,0 @@
extern crate cbindgen;
use std::env;
use std::path::Path;
use cbindgen::{Config, Builder};
fn main() {
let crate_env = env::var("CARGO_MANIFEST_DIR").unwrap();
let crate_path = Path::new(&crate_env);
let config = Config::from_root_or_default(crate_path);
Builder::new().with_crate(crate_path.to_str().unwrap())
.with_config(config)
.generate()
.expect("Cannot generate header file!")
.write_to_file("libdla.h");
}
-11
View File
@@ -1,11 +0,0 @@
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>
extern "C" {
bool dla_rust_disabled();
} // extern "C"
-4
View File
@@ -1,4 +0,0 @@
#[no_mangle]
pub extern "C" fn dla_rust_disabled() -> bool {
true
}
+4 -4
View File
@@ -8,13 +8,13 @@ pub mod model;
pub mod spaces; pub mod spaces;
pub trait Position: Add<Output=Self> + Serialize + Clone { pub trait Position: Add<Output=Self> + Serialize + Clone {
// const DIM: usize; const DIM: usize;
type Cartesian; // type Cartesian;
fn zero() -> Self; fn zero() -> Self;
fn abs(&self) -> f32; fn abs(&self) -> f32;
fn from_cartesian(cartesian: Self::Cartesian) -> Self; fn from_cartesian(cartesian: &[f32]) -> Self;
fn to_cartesian(&self) -> Self::Cartesian; fn to_cartesian(&self) -> Vec<f32>;
} }
pub trait GriddedPosition: Position { pub trait GriddedPosition: Position {
+8 -8
View File
@@ -2,15 +2,15 @@ use std::f32::consts::PI;
use std::ops::Add; use std::ops::Add;
use kiddo::distance::squared_euclidean; use kiddo::distance::squared_euclidean;
use rand::Rng; use rand::Rng;
use serde::Serialize; use serde::{Serialize, Deserialize};
use crate::system::sticker::Sticker; use crate::system::sticker::Sticker;
use crate::system::{Position, Storage}; use crate::system::{Position, Storage};
use crate::system::walker::Walker; use crate::system::walker::Walker;
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct P2 { pub struct P2 {
x: f32, pub x: f32,
y: f32, pub y: f32,
} }
impl P2 { impl P2 {
@@ -47,7 +47,7 @@ pub struct ContinuousStorage {
} }
impl Position for P2 { impl Position for P2 {
type Cartesian = [f32; 2]; const DIM: usize = 2;
fn zero() -> Self { fn zero() -> Self {
P2 { x: 0f32, y: 0f32 } P2 { x: 0f32, y: 0f32 }
@@ -57,12 +57,12 @@ impl Position for P2 {
(self.x.powi(2) + self.y.powi(2)).powf(0.5) (self.x.powi(2) + self.y.powi(2)).powf(0.5)
} }
fn from_cartesian(cartesian: Self::Cartesian) -> Self { fn from_cartesian(cartesian: &[f32]) -> Self {
P2 { x: cartesian[0], y: cartesian[1] } P2 { x: cartesian[0], y: cartesian[1] }
} }
fn to_cartesian(&self) -> Self::Cartesian { fn to_cartesian(&self) -> Vec<f32> {
[self.x, self.y] vec![self.x, self.y]
} }
} }
+4 -4
View File
@@ -51,7 +51,7 @@ pub struct ContinuousStorage {
} }
impl Position for P3 { impl Position for P3 {
type Cartesian = [f32; 3]; const DIM: usize = 3;
fn zero() -> Self { fn zero() -> Self {
P3 { x: 0f32, y: 0f32, z: 0f32 } P3 { x: 0f32, y: 0f32, z: 0f32 }
@@ -61,12 +61,12 @@ impl Position for P3 {
(self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).powf(0.5) (self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).powf(0.5)
} }
fn from_cartesian(cartesian: Self::Cartesian) -> Self { fn from_cartesian(cartesian: &[f32]) -> Self {
P3 { x: cartesian[0], y: cartesian[1], z: cartesian[3] } P3 { x: cartesian[0], y: cartesian[1], z: cartesian[3] }
} }
fn to_cartesian(&self) -> Self::Cartesian { fn to_cartesian(&self) -> Vec<f32> {
[self.x, self.y, self.z] vec![self.x, self.y, self.z]
} }
} }
+6 -5
View File
@@ -1,5 +1,6 @@
use std::ops::Add; use std::ops::Add;
use num_integer::Roots; use num_integer::Roots;
use num_traits::Pow;
use crate::system::{GriddedPosition, Position}; use crate::system::{GriddedPosition, Position};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
@@ -40,8 +41,7 @@ impl GriddedPosition for HexPosition {
} }
impl Position for HexPosition { impl Position for HexPosition {
// const DIM: usize = 2; const DIM: usize = 2;
type Cartesian = [f32; 2];
fn zero() -> Self { fn zero() -> Self {
HexPosition { q: 0, r: 0 } HexPosition { q: 0, r: 0 }
@@ -51,17 +51,18 @@ impl Position for HexPosition {
((self.q.pow(2) + self.r.pow(2) + self.q * self.r) as f32).sqrt() ((self.q.pow(2) + self.r.pow(2) + self.q * self.r) as f32).sqrt()
} }
fn from_cartesian(cartesian: Self::Cartesian) -> Self { fn from_cartesian(cartesian: &[f32]) -> Self {
let q = (1.0f32 / 3.0f32).sqrt() * cartesian[0] - 1.0 / 3.0 * cartesian[1]; let q = (1.0f32 / 3.0f32).sqrt() * cartesian[0] - 1.0 / 3.0 * cartesian[1];
let r = 2.0 / 3.0 * cartesian[1]; let r = 2.0 / 3.0 * cartesian[1];
Self { q: q as i32, r: r as i32 } Self { q: q as i32, r: r as i32 }
} }
fn to_cartesian(&self) -> Self::Cartesian { fn to_cartesian(&self) -> Vec<f32> {
let q = self.q as f32; let q = self.q as f32;
let r = self.r as f32; let r = self.r as f32;
[
vec![
3f32.sqrt() * q + 3f32.sqrt() / 2f32 * r, 3f32.sqrt() * q + 3f32.sqrt() / 2f32 * r,
(3. / 2.) * r (3. / 2.) * r
] ]
+1
View File
@@ -7,3 +7,4 @@ pub mod hexagonal;
pub mod continuous_3d; pub mod continuous_3d;
pub mod continuous_2d; pub mod continuous_2d;
pub mod nalg;
+148
View File
@@ -0,0 +1,148 @@
use std::ops::Add;
use itertools::Itertools;
use nalgebra::{EuclideanNorm, LpNorm, Matrix, Norm, OMatrix, SVector};
use num_traits::Pow;
use serde::{Serialize, Deserialize};
use crate::system::{GriddedPosition, Position, Storage};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Gridded<const D: usize>(SVector<i32, D>);
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Continuous<const D: usize>(SVector<f32, D>);
impl<const D: usize> Add for Continuous<D> {
type Output = Continuous<D>;
fn add(self, rhs: Self) -> Self::Output {
Continuous(self.0 + rhs.0)
}
}
impl<const D: usize> Position for Continuous<D> {
const DIM: usize = 0;
fn zero() -> Self {
Continuous(SVector::<f32, D>::zeros())
}
fn abs(&self) -> f32 {
self.0.norm()
}
fn from_cartesian(cartesian: &[f32]) -> Self {
Continuous(SVector::<f32, D>::from_fn(|i, _| cartesian[i]))
}
fn to_cartesian(&self) -> Vec<f32> {
self.0.as_slice().to_vec()
}
}
impl<const D: usize> Add for Gridded<D> {
type Output = Gridded<D>;
fn add(self, rhs: Self) -> Self::Output {
Gridded(self.0 + rhs.0)
}
}
impl<const D: usize> Position for Gridded<D> {
const DIM: usize = 0;
fn zero() -> Self {
Gridded(SVector::<i32, D>::zeros())
}
fn abs(&self) -> f32 {
(self.0.fold(0, |r, c| r + c.pow(2)) as f32).sqrt()
}
fn from_cartesian(cartesian: &[f32]) -> Self {
Gridded(SVector::<i32, D>::from_fn(|i, _| cartesian[i] as i32))
}
fn to_cartesian(&self) -> Vec<f32> {
self.0.as_slice()
.iter()
.map(|a| *a as f32)
.collect_vec()
}
}
pub struct KDSpace<const N: usize>(pub(crate) kiddo::KdTree<f32, (), N>);
impl<const D: usize> Storage<Gridded<D>> for KDSpace<D> {
fn is_occupied(&self, position: &Gridded<D>) -> bool {
let a = self.0.best_n_within(
&position.0.data.0[0].map(|i| i as f32),
0f32,
1,
&|a, b| {
LpNorm(1).metric_distance(
&SVector::<f32, D>::from_row_slice(a),
&SVector::<f32, D>::from_row_slice(b),
)
},
).unwrap();
!a.is_empty()
}
fn deposit(&mut self, position: &Gridded<D>) {
self.0.add(&position.0.data.0[0].map(|i| i as f32), ())
.expect("Failed to write to space")
}
}
impl<const D: usize> Storage<Continuous<D>> for KDSpace<D> {
fn is_occupied(&self, position: &Continuous<D>) -> bool {
let a = self.0.best_n_within(
&position.0.data.0[0],
0f32,
1,
&|a, b| {
EuclideanNorm.metric_distance(
&SVector::<f32, D>::from_row_slice(a),
&SVector::<f32, D>::from_row_slice(b),
)
},
).unwrap();
!a.is_empty()
}
fn deposit(&mut self, position: &Continuous<D>) {
self.0.add(&position.0.data.0[0], ())
.expect("Failed to write to space")
}
}
pub struct VectorStorage {
backing: Vec<bool>,
grid_size: usize,
}
impl<const D: usize> Storage<Gridded<D>> for VectorStorage {
fn is_occupied(&self, position: &Gridded<D>) -> bool {
let mut index: usize = 0;
for i in 0..D {
index += (position.0[i] + (self.grid_size as i32 / 2)) as usize * self.grid_size * i;
}
return self.backing[index];
}
fn deposit(&mut self, position: &Gridded<D>) {
let mut index: usize = 0;
for i in 0..D {
index += (position.0[i] + (self.grid_size as i32 / 2)) as usize * self.grid_size * i;
}
self.backing[index] = true;
}
}
+12 -12
View File
@@ -28,7 +28,7 @@ impl Add for Grid2D {
} }
impl Position for Grid2D { impl Position for Grid2D {
type Cartesian = [f32; 2]; const DIM: usize = 2;
fn zero() -> Self { fn zero() -> Self {
Grid2D { x: 0, y: 0 } Grid2D { x: 0, y: 0 }
@@ -38,15 +38,15 @@ impl Position for Grid2D {
(((self.x * self.x) + (self.y * self.y)) as f32).powf(0.5) (((self.x * self.x) + (self.y * self.y)) as f32).powf(0.5)
} }
fn from_cartesian(cartesian: Self::Cartesian) -> Self { fn from_cartesian(cartesian: &[f32]) -> Self {
Grid2D { Grid2D {
x: cartesian[0] as i32, x: cartesian[0] as i32,
y: cartesian[1] as i32, y: cartesian[1] as i32,
} }
} }
fn to_cartesian(&self) -> Self::Cartesian { fn to_cartesian(&self) -> Vec<f32> {
[self.x as f32, self.y as f32] vec![self.x as f32, self.y as f32]
} }
} }
@@ -113,17 +113,17 @@ impl Add for Grid3D {
#[test] #[test]
fn grid3_add_test() { fn grid3_add_test() {
assert_eq!( assert_eq!(
Grid3D::from_cartesian([0f32, 0f32, 0f32]) + Grid3D::from_cartesian([0f32, 1f32, 0f32]), Grid3D::from_cartesian(&[0f32, 0f32, 0f32]) + Grid3D::from_cartesian(&[0f32, 1f32, 0f32]),
Grid3D::from_cartesian([0f32, 1f32, 0f32]) Grid3D::from_cartesian(&[0f32, 1f32, 0f32])
); );
assert_eq!( assert_eq!(
Grid3D::from_cartesian([5.0, 3.0, 1.0]) + Grid3D::from_cartesian([-2.0, 5.0, 100.0]), Grid3D::from_cartesian(&[5.0, 3.0, 1.0]) + Grid3D::from_cartesian(&[-2.0, 5.0, 100.0]),
Grid3D::from_cartesian([3.0, 8.0, 101.0]) Grid3D::from_cartesian(&[3.0, 8.0, 101.0])
); );
} }
impl Position for Grid3D { impl Position for Grid3D {
type Cartesian = [f32; 3]; const DIM: usize = 3;
fn zero() -> Self { fn zero() -> Self {
Grid3D { x: 0, y: 0, z: 0 } Grid3D { x: 0, y: 0, z: 0 }
@@ -133,7 +133,7 @@ impl Position for Grid3D {
(((self.x * self.x) + (self.y * self.y) + (self.z * self.z)) as f32).powf(0.5) (((self.x * self.x) + (self.y * self.y) + (self.z * self.z)) as f32).powf(0.5)
} }
fn from_cartesian(cartesian: Self::Cartesian) -> Self { fn from_cartesian(cartesian: &[f32]) -> Self {
Self { Self {
x: cartesian[0] as i32, x: cartesian[0] as i32,
y: cartesian[1] as i32, y: cartesian[1] as i32,
@@ -141,8 +141,8 @@ impl Position for Grid3D {
} }
} }
fn to_cartesian(&self) -> Self::Cartesian { fn to_cartesian(&self) -> Vec<f32> {
[self.x as f32, self.y as f32, self.z as f32] vec![self.x as f32, self.y as f32, self.z as f32]
} }
} }
-1
View File
@@ -15,7 +15,6 @@ impl VectorStorage {
impl<P: GriddedPosition> Storage<P> for VectorStorage { impl<P: GriddedPosition> Storage<P> for VectorStorage {
fn is_occupied(&self, position: &P) -> bool { fn is_occupied(&self, position: &P) -> bool {
let i = position.linear_index(self.grid_size); let i = position.linear_index(self.grid_size);
println!("{i}");
return self.backing[i]; return self.backing[i];
} }
+3 -3
View File
@@ -17,7 +17,7 @@ impl Spawner<Grid2D> for UniformSpawner {
let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; let theta = rng.gen_range(0f32..1.0) * 2.0 * PI;
let (x, y) = (radius * theta.cos(), radius * theta.sin()); let (x, y) = (radius * theta.cos(), radius * theta.sin());
Grid2D::from_cartesian([x, y]) Grid2D::from_cartesian(&[x, y])
} }
} }
@@ -26,7 +26,7 @@ impl Spawner<HexPosition> for UniformSpawner {
let theta = rng.gen_range(0f32..1.0) * 2.0 * PI; let theta = rng.gen_range(0f32..1.0) * 2.0 * PI;
let (x, y) = (radius * theta.cos(), radius * theta.sin()); let (x, y) = (radius * theta.cos(), radius * theta.sin());
HexPosition::from_cartesian([x, y]) HexPosition::from_cartesian(&[x, y])
} }
} }
@@ -41,7 +41,7 @@ impl Spawner<Grid3D> for UniformSpawner {
radius * theta.cos() radius * theta.cos()
); );
Grid3D::from_cartesian([x, y, z]) Grid3D::from_cartesian(&[x, y, z])
} }
} }
-1
View File
@@ -1,5 +1,4 @@
use std::hash::Hash; use std::hash::Hash;
use bevy::render::render_resource::encase::private::RuntimeSizedArray;
use itertools::Itertools; use itertools::Itertools;
use rand::distributions::Slice; use rand::distributions::Slice;
use rand::prelude::Rng; use rand::prelude::Rng;
+1 -1
View File
@@ -5,7 +5,7 @@ use polars::io::RowCount;
use rayon::prelude::*; use rayon::prelude::*;
use walkdir::WalkDir; use walkdir::WalkDir;
pub fn main(cli: &DataAnalysisCli) { pub(crate) fn main(cli: &DataAnalysisCli) {
let a: Vec<_> = WalkDir::new(&cli.sp_dir) let a: Vec<_> = WalkDir::new(&cli.sp_dir)
.into_iter() .into_iter()
.par_bridge() .par_bridge()
-1
View File
@@ -1,6 +1,5 @@
use std::fs::File; use std::fs::File;
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
use bevy::tasks::ParallelSlice;
use crate::system::spaces::square_grid::{Grid2D, Grid3D}; use crate::system::spaces::square_grid::{Grid2D, Grid3D};
use itertools::{Itertools, MinMaxResult}; use itertools::{Itertools, MinMaxResult};
use clap::Parser; use clap::Parser;
+62 -30
View File
@@ -9,12 +9,16 @@ use crate::system::model::HistoryLine;
use crate::system::spaces::square_grid::Grid2D; use crate::system::spaces::square_grid::Grid2D;
use clap::Parser; use clap::Parser;
use colorous::Color; use colorous::Color;
use itertools::Itertools;
use num_traits::real::Real;
use num_traits::Signed;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Deserialize; use serde::Deserialize;
use svg::Node; use svg::Node;
use svg::node::element::Rectangle; use svg::node::element::{Circle, Polygon, Rectangle};
use crate::RenderCli; use crate::{RenderCli, Space};
use crate::system::Position; use crate::system::{GriddedPosition, Position};
use crate::system::spaces::continuous_2d::P2;
use crate::system::spaces::hexagonal::HexPosition; use crate::system::spaces::hexagonal::HexPosition;
use crate::tools::read; use crate::tools::read;
@@ -26,68 +30,96 @@ struct Args {
} }
trait ToSvg { trait ToSvg {
fn to_svg(&self, size: i32, colour: Color) -> Box<dyn Node>; fn to_svg(&self, colour: Color) -> Box<dyn Node>;
} }
impl ToSvg for Grid2D { impl ToSvg for Grid2D {
fn to_svg(&self, size: i32, colour: Color) -> Box<dyn Node> { fn to_svg(&self, colour: Color) -> Box<dyn Node> {
Box::new(Rectangle::new() Box::new(Rectangle::new()
.set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b)) .set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b))
.set("width", size) .set("width", 1)
.set("height", size) .set("height", 1)
.set("x", self.x * size) .set("x", self.x)
.set("y", self.y * size)) .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 { impl ToSvg for HexPosition {
fn to_svg(&self, size: i32, colour: Color) -> Box<dyn Node> { fn to_svg(&self, colour: Color) -> Box<dyn Node> {
let points = [ 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] [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 size = size as f32; let a = 256.0;
// let a = 400.0;
let b = points.map(|x| [ let b = points.map(|x| [
(x[0] / 512.0) * (size), (x[0] / a),
(x[1] / 512.0) * (size)] (x[1] / a)]
); );
let c = b.map(|p| format!("{},{}", p[0], p[1])).join(" "); let c = b.map(|p| format!("{},{}", p[0], p[1])).join(" ");
let [x, y] = self.to_cartesian(); let cartesian = self.to_cartesian();
Box::new(Rectangle::new() Box::new(Polygon::new()
.set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b)) .set("fill", format!("rgb({}, {}, {})", colour.r, colour.g, colour.b))
.set("x", x * size) .set("points", c)
.set("y", y * size)) .set("transform", format!("translate({}, {})", cartesian[0], cartesian[1])))
} }
} }
pub(crate) fn main(args: &RenderCli) { pub fn compute_max_size<P: Position>(positions: &[P]) -> f32 {
let positions: Vec<Grid2D> = read::<Grid2D>(&args.path, args.format); let mut positions = positions.iter().map(P::to_cartesian);
let mut maximums: Vec<f32> = positions.next().unwrap();
let size: i32 = args.image_size as i32; for cartesian in positions {
let max_x = positions.iter().max_by(|a, b| a.x.abs().cmp(&b.x.abs())).unwrap().x.abs(); for i in 0..maximums.len() {
let max_y = positions.iter().max_by(|a, b| a.y.abs().cmp(&b.y.abs())).unwrap().y.abs(); maximums[i] = maximums[i].abs().max(cartesian[i].abs());
let max_size = max_x.max(max_y) * size; }
}
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() let mut svg = svg::Document::new()
.set("width", max_size * size) .set("width", args.image_size)
.set("height", max_size * size) .set("height", args.image_size)
.set("viewBox", format!("{} {} {} {}", -max_size, -max_size, max_size * 2, max_size * 2)); .set("viewBox", format!("{} {} {} {}", -max_size, -max_size, max_size * 2.0, max_size * 2.0));
svg.append(Rectangle::new() svg.append(Rectangle::new()
.set("fill", "white") .set("fill", "white")
.set("width", max_size * 2) .set("width", max_size * 2.0)
.set("height", max_size * 2) .set("height", max_size * 2.0)
.set("x", -max_size) .set("x", -max_size)
.set("y", -max_size) .set("y", -max_size)
); );
for (n, position) in positions.iter().enumerate() { for (n, position) in positions.iter().enumerate() {
let colour = if args.colour { colorous::VIRIDIS.eval_rational(n, positions.len()) } else { Color::default() }; let colour = if args.colour { colorous::VIRIDIS.eval_rational(n, positions.len()) } else { Color::default() };
svg.append(position.to_svg(size, colour)); svg.append(position.to_svg(colour));
} }
svg::write(File::create(&args.output).unwrap(), &svg).unwrap(); 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),
}
}
+4 -2
View File
@@ -1,5 +1,6 @@
#![feature(generic_const_exprs)] #![feature(generic_const_exprs)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(array_zip)]
use std::fs::File; use std::fs::File;
use std::ops::{Index, Mul}; use std::ops::{Index, Mul};
@@ -33,6 +34,8 @@ enum ToolsCli {
#[derive(clap::ValueEnum, Clone, Debug, Copy)] #[derive(clap::ValueEnum, Clone, Debug, Copy)]
enum Space { enum Space {
Grid2D, Grid2D,
Continuous2D,
Hex,
} }
#[derive(Debug, Args)] #[derive(Debug, Args)]
@@ -73,8 +76,7 @@ fn main() -> anyhow::Result<()> {
match args { match args {
ToolsCli::Render(cli) => tools::render::main(&cli), ToolsCli::Render(cli) => tools::render::main(&cli),
ToolsCli::BoxCount(cli) => tools::boxcount::main(&cli), ToolsCli::BoxCount(cli) => tools::boxcount::main(&cli),
ToolsCli::DataAnalysis(cli) => { ToolsCli::DataAnalysis(cli) => tools::analysis::main(&cli),
}
} }
Ok(()) Ok(())
-108
View File
@@ -1,108 +0,0 @@
//! A simplified implementation of the classic game "Breakout".
use bevy::{
prelude::*,
};
// These constants are defined in `Transform` units.
// Using the default 2D camera they correspond 1:1 with screen pixels.
const GAP_BETWEEN_PADDLE_AND_FLOOR: f32 = 60.0;
const LEFT_WALL: f32 = -450.;
const RIGHT_WALL: f32 = 450.;
// y coordinates
const BOTTOM_WALL: f32 = -300.;
const TOP_WALL: f32 = 300.;
const PARTICLE_SIZE: Vec2 = Vec2::new(10., 10.);
// These values are exact
const GAP_BETWEEN_PADDLE_AND_BRICKS: f32 = 270.0;
const GAP_BETWEEN_BRICKS: f32 = 5.0;
// These values are lower bounds, as the number of bricks is computed
const GAP_BETWEEN_BRICKS_AND_CEILING: f32 = 20.0;
const GAP_BETWEEN_BRICKS_AND_SIDES: f32 = 20.0;
const BACKGROUND_COLOR: Color = Color::rgb(0.9, 0.9, 0.9);
const BRICK_COLOR: Color = Color::rgb(0.5, 0.5, 1.0);
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(ClearColor(BACKGROUND_COLOR))
.add_startup_system(setup)
.add_system(bevy::window::close_on_esc)
.run();
}
#[derive(Component)]
struct Brick;
// Add the game's entities to our world
fn setup(
mut commands: Commands,
) {
// Camera
commands.spawn(Camera2dBundle::default());
// Paddle
let paddle_y = BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR;
// Bricks
// Negative scales result in flipped sprites / meshes,
// which is definitely not what we want here
assert!(PARTICLE_SIZE.x > 0.0);
assert!(PARTICLE_SIZE.y > 0.0);
let total_width_of_bricks = (RIGHT_WALL - LEFT_WALL) - 2. * GAP_BETWEEN_BRICKS_AND_SIDES;
let bottom_edge_of_bricks = paddle_y + GAP_BETWEEN_PADDLE_AND_BRICKS;
let total_height_of_bricks = TOP_WALL - bottom_edge_of_bricks - GAP_BETWEEN_BRICKS_AND_CEILING;
assert!(total_width_of_bricks > 0.0);
assert!(total_height_of_bricks > 0.0);
// Given the space available, compute how many rows and columns of bricks we can fit
let n_columns = (total_width_of_bricks / (PARTICLE_SIZE.x + GAP_BETWEEN_BRICKS)).floor() as usize;
let n_rows = (total_height_of_bricks / (PARTICLE_SIZE.y + GAP_BETWEEN_BRICKS)).floor() as usize;
let n_vertical_gaps = n_columns - 1;
// Because we need to round the number of columns,
// the space on the top and sides of the bricks only captures a lower bound, not an exact value
let center_of_bricks = (LEFT_WALL + RIGHT_WALL) / 2.0;
let left_edge_of_bricks = center_of_bricks
// Space taken up by the bricks
- (n_columns as f32 / 2.0 * PARTICLE_SIZE.x)
// Space taken up by the gaps
- n_vertical_gaps as f32 / 2.0 * GAP_BETWEEN_BRICKS;
// In Bevy, the `translation` of an entity describes the center point,
// not its bottom-left corner
let offset_x = left_edge_of_bricks + PARTICLE_SIZE.x / 2.;
let offset_y = bottom_edge_of_bricks + PARTICLE_SIZE.y / 2.;
for row in 0..n_rows {
for column in 0..n_columns {
let brick_position = Vec2::new(
offset_x + column as f32 * (PARTICLE_SIZE.x + GAP_BETWEEN_BRICKS),
offset_y + row as f32 * (PARTICLE_SIZE.y + GAP_BETWEEN_BRICKS),
);
// brick
commands.spawn((
SpriteBundle {
sprite: Sprite {
color: BRICK_COLOR,
..default()
},
transform: Transform {
translation: brick_position.extend(0.0),
scale: Vec3::new(PARTICLE_SIZE.x, PARTICLE_SIZE.y, 1.0),
..default()
},
..default()
},
Brick,
));
}
}
}