Compare commits
No commits in common. "a85e8e33eae78b62da46483386ae53a68819240f" and "73bb0f8ed17d5779d5dcdd26209119510c4f5e1a" have entirely different histories.
a85e8e33ea
...
73bb0f8ed1
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
.idea
|
.idea
|
||||||
*.csv
|
*.csv
|
||||||
/*.json
|
|
||||||
*.svg
|
|
||||||
/data
|
|
||||||
|
|||||||
1143
Cargo.lock
generated
1143
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,10 +18,6 @@ path = "src/main.rs"
|
|||||||
name = "ui"
|
name = "ui"
|
||||||
path = "src/ui.rs"
|
path = "src/ui.rs"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tools"
|
|
||||||
path = "src/tools_cli.rs"
|
|
||||||
|
|
||||||
# Set the default for crate.
|
# Set the default for crate.
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
@ -36,7 +32,7 @@ opt-level = 3
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.1.8", features = ["derive"] }
|
clap = { version = "4.1.8", features = ["derive"] }
|
||||||
bevy = { version = "0.10.0" }
|
bevy = { version = "0.9.1" }
|
||||||
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"] }
|
||||||
@ -46,9 +42,6 @@ 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 = "0.32.2"
|
||||||
kiddo = "0.2.5"
|
kiddo = "0.2.5"
|
||||||
anyhow = "1.0.69"
|
|
||||||
itertools = "0.10.5"
|
|
||||||
svg = "0.13.0"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cbindgen = "0.24.3"
|
cbindgen = "0.24.3"
|
||||||
|
|||||||
10002
balls.json
Normal file
10002
balls.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ pub enum OutputFormat {
|
|||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct InitialCli {
|
pub struct InitialCli {
|
||||||
pub grid_size: u32,
|
pub grid_size: u32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
@ -18,11 +18,6 @@ pub struct StickProbabilityCli {
|
|||||||
pub stick_probability: f32,
|
pub stick_probability: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
|
||||||
pub struct KDStickProbabilityCli {
|
|
||||||
pub stick_probability: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct BallsCli {
|
pub struct BallsCli {
|
||||||
pub ball_radius: f32,
|
pub ball_radius: f32,
|
||||||
@ -40,23 +35,12 @@ pub struct SurfaceProbabilityMeasureCli {
|
|||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum PCM {
|
pub enum PCM {
|
||||||
/// Traditional DLA model in a fixed-size 2D grid
|
|
||||||
Initial(InitialCli),
|
Initial(InitialCli),
|
||||||
|
|
||||||
/// Traditional DLA model in a fixed-size 2D grid with a probabilistic sticking component
|
|
||||||
StickProbability(StickProbabilityCli),
|
StickProbability(StickProbabilityCli),
|
||||||
|
Grid3KD(StickProbabilityCli),
|
||||||
/// Traditional DLA model in unbounded 3D space with probabilistic sticking
|
Grid3(StickProbabilityCli),
|
||||||
Grid3d(KDStickProbabilityCli),
|
|
||||||
|
|
||||||
/// Traditional DLA model in unbounded 3D space with probabilistic sticking and off-axis walks
|
|
||||||
Grid3dOffAxis(KDStickProbabilityCli),
|
|
||||||
|
|
||||||
Hex(StickProbabilityCli),
|
Hex(StickProbabilityCli),
|
||||||
|
|
||||||
Balls2d(BallsCli),
|
|
||||||
Balls(BallsCli),
|
Balls(BallsCli),
|
||||||
|
|
||||||
SurfaceProbabilityMeasure(SurfaceProbabilityMeasureCli),
|
SurfaceProbabilityMeasure(SurfaceProbabilityMeasureCli),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,13 +49,13 @@ pub struct ModelCli {
|
|||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub preconfigured_model: PCM,
|
pub preconfigured_model: PCM,
|
||||||
|
|
||||||
#[arg(long, help = "Set the maximum number of ticks the system will run before exiting, will still produce data after this has been reached.")]
|
#[arg(long)]
|
||||||
pub max_frames: Option<usize>,
|
pub max_frames: Option<usize>,
|
||||||
|
|
||||||
#[arg(short = 'N', long)]
|
#[arg(short = 'N', long)]
|
||||||
pub max_particles: usize,
|
pub max_particles: usize,
|
||||||
|
|
||||||
#[arg(long, short, help = "Specifying a seed allows for deterministic runs (when combined with other parameters such as -N)")]
|
#[arg(long, short)]
|
||||||
pub seed: u64,
|
pub seed: u64,
|
||||||
|
|
||||||
#[arg(value_enum, short, long, default_value_t = OutputFormat::Positions)]
|
#[arg(value_enum, short, long, default_value_t = OutputFormat::Positions)]
|
||||||
@ -80,9 +64,6 @@ pub struct ModelCli {
|
|||||||
#[arg(value_enum, short, long)]
|
#[arg(value_enum, short, long)]
|
||||||
pub output: PathBuf,
|
pub output: PathBuf,
|
||||||
|
|
||||||
#[arg(long, default_value_t = false, help = "If the output file already exists, skipping running. Useful when running deterministically.")]
|
|
||||||
pub preserve_existing: bool,
|
|
||||||
|
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub notify_every: Option<usize>,
|
pub notify_every: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::fs::{create_dir_all, File};
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use crate::cli::cli::OutputFormat;
|
use crate::cli::cli::OutputFormat;
|
||||||
@ -13,14 +13,6 @@ pub fn write<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spawner<P>, S
|
|||||||
format: OutputFormat,
|
format: OutputFormat,
|
||||||
output: &Path,
|
output: &Path,
|
||||||
) {
|
) {
|
||||||
// If the parent does not exist (and we are not in the root or operating a relative 1-component-length path))
|
|
||||||
// create it. This greatly simplifies the harness code.
|
|
||||||
if let Some(parent) = output.parent() &&
|
|
||||||
parent.to_str().map(|x| x != "").unwrap_or(true) &&
|
|
||||||
!parent.exists() {
|
|
||||||
create_dir_all(parent).expect("Failed to create path to output");
|
|
||||||
}
|
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
OutputFormat::FullDataJson => write_json_full_data(sys, output),
|
OutputFormat::FullDataJson => write_json_full_data(sys, output),
|
||||||
OutputFormat::Positions => write_csv_positions(sys, output),
|
OutputFormat::Positions => write_csv_positions(sys, output),
|
||||||
@ -37,11 +29,8 @@ fn write_csv_positions<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spa
|
|||||||
.map(|line| &line.position)
|
.map(|line| &line.position)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
positions
|
wtr.serialize(positions)
|
||||||
.iter()
|
.unwrap();
|
||||||
.for_each(|position|
|
|
||||||
wtr.serialize(position).expect("Failed to write row")
|
|
||||||
);
|
|
||||||
|
|
||||||
wtr.flush()
|
wtr.flush()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -50,6 +39,6 @@ fn write_csv_positions<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spa
|
|||||||
fn write_json_full_data<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spawner<P>, St: Sticker<P, S>>(sys: &DLASystem<R, P, S, W, Sp, St>, output_path: &Path) {
|
fn write_json_full_data<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spawner<P>, St: Sticker<P, S>>(sys: &DLASystem<R, P, S, W, Sp, St>, output_path: &Path) {
|
||||||
let file = File::create(output_path).expect("Failed to open file");
|
let file = File::create(output_path).expect("Failed to open file");
|
||||||
|
|
||||||
serde_json::to_writer_pretty(file, &sys.history)
|
serde_json::to_writer(file, &sys.history)
|
||||||
.expect("Failed to write json");
|
.expect("Failed to write json");
|
||||||
}
|
}
|
||||||
|
|||||||
77
src/clib.rs
77
src/clib.rs
@ -2,3 +2,80 @@
|
|||||||
pub extern "C" fn dla_rust_disabled() -> bool {
|
pub extern "C" fn dla_rust_disabled() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #![feature(array_zip)]
|
||||||
|
//
|
||||||
|
// use system::Storage;
|
||||||
|
// use crate::system::spaces::square_grid::Grid2D;
|
||||||
|
// use crate::system::spaces::VectorStorage;
|
||||||
|
//
|
||||||
|
// mod system;
|
||||||
|
//
|
||||||
|
// #[derive(Eq, PartialEq, Debug)]
|
||||||
|
// #[repr(C)]
|
||||||
|
// pub struct CPosition(i32, i32);
|
||||||
|
//
|
||||||
|
// pub struct CStorage(VectorStorage);
|
||||||
|
//
|
||||||
|
// #[no_mangle]
|
||||||
|
// pub extern "C" fn storage_new(grid_size: u32) -> &'static mut CStorage {
|
||||||
|
// let mut pntr = Box::new(CStorage(VectorStorage::new(grid_size)));
|
||||||
|
// Box::leak(pntr)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[no_mangle]
|
||||||
|
// pub extern "C" fn storage_at(storage: &CStorage, i: i32, j: i32) -> bool {
|
||||||
|
// storage.0.at(&Grid2D { x: i, y: j })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[no_mangle]
|
||||||
|
// pub extern "C" fn storage_deposit(storage: &mut CStorage, i: i32, j: i32, val: u8) {
|
||||||
|
// storage.0.inner.(&Grid2D { x: i, y: j }, val == 1);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[no_mangle]
|
||||||
|
// pub extern "C" fn walk(d: u32, i: i32, j: i32) -> CPosition {
|
||||||
|
// return test::b(d, i, j);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // mod test {
|
||||||
|
// // use num_integer::Integer;
|
||||||
|
// // use crate::CPosition;
|
||||||
|
// // use crate::system::grid::Grid2D;
|
||||||
|
// //
|
||||||
|
// // pub(crate) fn a(d: u32, i: i32, j: i32) -> CPosition {
|
||||||
|
// // match d {
|
||||||
|
// // 0 => CPosition(i + 1, j),
|
||||||
|
// // 1 => CPosition(i - 1, j),
|
||||||
|
// // 2 => CPosition(i, j + 1),
|
||||||
|
// // 3 => CPosition(i, j - 1),
|
||||||
|
// // _ => panic!("Ahh"),
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // pub(crate) fn b(d: u32, i: i32, j: i32) -> CPosition {
|
||||||
|
// // let (dim, sign) = d.div_rem(&2);
|
||||||
|
// // let sign = if sign == 0 { 1 } else { -1 };
|
||||||
|
// // // HACK: Our conventin and the MVA are different, since we are trying to strangle fig this, quick hack.
|
||||||
|
// // let offset = Grid2D::in_direction(dim, sign);
|
||||||
|
// // let next = Grid2D { x: i, y: j } + offset;
|
||||||
|
// //
|
||||||
|
// // CPosition(next.x, next.y)
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // #[test]
|
||||||
|
// // fn test() {
|
||||||
|
// // let d = [0, 1, 2, 3];
|
||||||
|
// // d.iter()
|
||||||
|
// // .map(|d| d.div_rem(&2))
|
||||||
|
// // .for_each(|p| println!("{p:?}"));
|
||||||
|
// // }
|
||||||
|
// //
|
||||||
|
// // #[test]
|
||||||
|
// // fn alignment() {
|
||||||
|
// // let d = [0, 1, 2, 3];
|
||||||
|
// // d.iter()
|
||||||
|
// // .map(|d| (a(*d, 0, 0), b(*d, 0, 0)))
|
||||||
|
// // .for_each(|p| assert_eq!(p.0, p.1));
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|||||||
51
src/main.rs
51
src/main.rs
@ -2,22 +2,22 @@
|
|||||||
#![feature(generic_const_exprs)]
|
#![feature(generic_const_exprs)]
|
||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use crate::cli::{drive_system};
|
use crate::cli::{drive_system};
|
||||||
use crate::cli::cli::{StickProbabilityCli, InitialCli, BallsCli, PCM, ModelCli, SurfaceProbabilityMeasureCli, KDStickProbabilityCli};
|
use crate::cli::cli::{StickProbabilityCli, InitialCli, BallsCli, PCM, ModelCli, SurfaceProbabilityMeasureCli};
|
||||||
use crate::cli::cli::PCM::Balls2d;
|
|
||||||
use crate::cli::output::write;
|
use crate::cli::output::write;
|
||||||
use crate::surface_probability_measure::{LoggerSticker, ReadOnlyVectorStorage};
|
use crate::surface_probability_measure::{LoggerSticker, ReadOnlyVectorStorage};
|
||||||
use crate::system::model::DLASystem;
|
use crate::system::model::DLASystem;
|
||||||
use crate::system::spaces::continuous_3d::{ContinuousSticker, ContinuousStorage, ContinuousWalker};
|
use crate::system::spaces::continuous::{ContinuousSticker, ContinuousStorage, ContinuousWalker};
|
||||||
use crate::system::spaces::hexagonal::HexPosition;
|
use crate::system::spaces::hexagonal::HexPosition;
|
||||||
use crate::system::spaces::kd_grid::{KDSpace};
|
use crate::system::spaces::kd_grid::{KDGrid, KDGrid2Sticker};
|
||||||
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
||||||
use crate::system::spaces::VectorStorage;
|
use crate::system::spaces::VectorStorage;
|
||||||
use crate::system::spawner::UniformSpawner;
|
use crate::system::spawner::UniformSpawner;
|
||||||
use crate::system::sticker::{ProbabilisticSticking, SimpleSticking};
|
use crate::system::sticker::{ProbabilisticSticking, SimpleSticking};
|
||||||
use crate::system::walker::{DiagonalRandomWalker, LocalRandomWalker, Walker};
|
use crate::system::walker::LocalRandomWalker;
|
||||||
|
|
||||||
mod system;
|
mod system;
|
||||||
mod surface_probability_measure;
|
mod surface_probability_measure;
|
||||||
@ -26,11 +26,6 @@ mod cli;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let cli = ModelCli::parse();
|
let cli = ModelCli::parse();
|
||||||
|
|
||||||
if cli.preserve_existing && cli.output.exists() {
|
|
||||||
println!("Skipping model execution as output file '{}' already exists", cli.output.to_str().unwrap());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Running: {:?}", cli);
|
println!("Running: {:?}", cli);
|
||||||
|
|
||||||
match cli.preconfigured_model {
|
match cli.preconfigured_model {
|
||||||
@ -54,7 +49,7 @@ fn main() {
|
|||||||
VectorStorage::new(grid_size, 2),
|
VectorStorage::new(grid_size, 2),
|
||||||
LocalRandomWalker,
|
LocalRandomWalker,
|
||||||
UniformSpawner,
|
UniformSpawner,
|
||||||
ProbabilisticSticking::new(stick_probability).unwrap(),
|
ProbabilisticSticking { stick_probability },
|
||||||
cli.max_particles,
|
cli.max_particles,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -62,13 +57,13 @@ fn main() {
|
|||||||
write(&sys, cli.format, &cli.output);
|
write(&sys, cli.format, &cli.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
PCM::Grid3d(KDStickProbabilityCli { stick_probability }) => {
|
PCM::Grid3KD(StickProbabilityCli { grid_size, stick_probability }) => {
|
||||||
let mut sys = DLASystem::<_, Grid3D, _, _, _, _>::new(
|
let mut sys = DLASystem::<_, Grid2D, _, _, _, _>::new(
|
||||||
SmallRng::seed_from_u64(cli.seed),
|
SmallRng::seed_from_u64(cli.seed),
|
||||||
KDSpace::new(),
|
KDGrid { inner: kiddo::KdTree::new() },
|
||||||
LocalRandomWalker,
|
LocalRandomWalker,
|
||||||
UniformSpawner,
|
UniformSpawner,
|
||||||
ProbabilisticSticking::new(stick_probability).unwrap(),
|
KDGrid2Sticker { stick_probability },
|
||||||
cli.max_particles,
|
cli.max_particles,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -76,13 +71,13 @@ fn main() {
|
|||||||
write(&sys, cli.format, &cli.output);
|
write(&sys, cli.format, &cli.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
PCM::Grid3dOffAxis(KDStickProbabilityCli { stick_probability }) => {
|
PCM::Grid3(StickProbabilityCli { grid_size, stick_probability }) => {
|
||||||
let mut sys = DLASystem::<_, Grid3D, _, _, _, _>::new(
|
let mut sys = DLASystem::<_, Grid3D, _, _, _, _>::new(
|
||||||
SmallRng::seed_from_u64(cli.seed),
|
SmallRng::seed_from_u64(cli.seed),
|
||||||
KDSpace::new(),
|
VectorStorage::new(grid_size, 3),
|
||||||
DiagonalRandomWalker,
|
LocalRandomWalker,
|
||||||
UniformSpawner,
|
UniformSpawner,
|
||||||
ProbabilisticSticking::new(stick_probability).unwrap(),
|
ProbabilisticSticking { stick_probability },
|
||||||
cli.max_particles,
|
cli.max_particles,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -96,23 +91,7 @@ fn main() {
|
|||||||
VectorStorage::new(grid_size, 2),
|
VectorStorage::new(grid_size, 2),
|
||||||
LocalRandomWalker,
|
LocalRandomWalker,
|
||||||
UniformSpawner,
|
UniformSpawner,
|
||||||
ProbabilisticSticking::new(stick_probability).unwrap(),
|
ProbabilisticSticking { stick_probability },
|
||||||
cli.max_particles,
|
|
||||||
);
|
|
||||||
|
|
||||||
drive_system(&mut sys, cli.max_frames, cli.notify_every);
|
|
||||||
write(&sys, cli.format, &cli.output);
|
|
||||||
}
|
|
||||||
|
|
||||||
PCM::Balls2d(BallsCli { ball_radius, stick_distance, walk_step }) => {
|
|
||||||
use system::spaces::continuous_2d;
|
|
||||||
|
|
||||||
let mut sys = DLASystem::new(
|
|
||||||
SmallRng::seed_from_u64(cli.seed),
|
|
||||||
continuous_2d::ContinuousStorage { inner: kiddo::KdTree::new(), ball_radius_sq: ball_radius * ball_radius },
|
|
||||||
continuous_2d::ContinuousWalker { walk_step },
|
|
||||||
UniformSpawner,
|
|
||||||
continuous_2d::ContinuousSticker { range_sq: stick_distance * stick_distance },
|
|
||||||
cli.max_particles,
|
cli.max_particles,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -8,13 +8,11 @@ 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;
|
|
||||||
|
|
||||||
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::DIM]) -> Self;
|
||||||
fn to_cartesian(&self) -> Self::Cartesian;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GriddedPosition: Position {
|
pub trait GriddedPosition: Position {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
use crate::system::{Position, Storage};
|
use crate::system::{Position, Storage};
|
||||||
use crate::system::spawner::Spawner;
|
use crate::system::spawner::Spawner;
|
||||||
use crate::system::sticker::Sticker;
|
use crate::system::sticker::Sticker;
|
||||||
use crate::system::walker::Walker;
|
use crate::system::walker::Walker;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize)]
|
||||||
pub struct HistoryLine<P: Position> {
|
pub struct HistoryLine<P: Position> {
|
||||||
pub frame: usize,
|
pub frame: usize,
|
||||||
pub cluster_radius: f32,
|
pub cluster_radius: f32,
|
||||||
|
|||||||
@ -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,13 +61,9 @@ 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::DIM]) -> 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 {
|
|
||||||
[self.x, self.y, self.z]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Storage<P3> for ContinuousStorage {
|
impl Storage<P3> for ContinuousStorage {
|
||||||
@ -1,102 +0,0 @@
|
|||||||
use std::f32::consts::PI;
|
|
||||||
use std::ops::Add;
|
|
||||||
use kiddo::distance::squared_euclidean;
|
|
||||||
use rand::Rng;
|
|
||||||
use serde::Serialize;
|
|
||||||
use crate::system::sticker::Sticker;
|
|
||||||
use crate::system::{Position, Storage};
|
|
||||||
use crate::system::walker::Walker;
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Clone)]
|
|
||||||
pub struct P2 {
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl P2 {
|
|
||||||
fn as_arr(&self) -> [f32; 2] {
|
|
||||||
[self.x, self.y]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn random_with_radius<R: Rng>(rng: &mut R, radius: f32) -> P2 {
|
|
||||||
let theta = rng.gen_range(0f32..1.0) * 2.0 * PI;
|
|
||||||
|
|
||||||
let (x, y) = (
|
|
||||||
radius * theta.sin(),
|
|
||||||
radius * theta.cos(),
|
|
||||||
);
|
|
||||||
|
|
||||||
P2 { x, y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add for P2 {
|
|
||||||
type Output = P2;
|
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
|
||||||
P2 {
|
|
||||||
x: self.x + rhs.x,
|
|
||||||
y: self.y + rhs.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ContinuousStorage {
|
|
||||||
pub inner: kiddo::KdTree<f32, (), 2>,
|
|
||||||
pub ball_radius_sq: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Position for P2 {
|
|
||||||
type Cartesian = [f32; 2];
|
|
||||||
|
|
||||||
fn zero() -> Self {
|
|
||||||
P2 { x: 0f32, y: 0f32 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn abs(&self) -> f32 {
|
|
||||||
(self.x.powi(2) + self.y.powi(2)).powf(0.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_cartesian(cartesian: Self::Cartesian) -> Self {
|
|
||||||
P2 { x: cartesian[0], y: cartesian[1] }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_cartesian(&self) -> Self::Cartesian {
|
|
||||||
[self.x, self.y]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Storage<P2> for ContinuousStorage {
|
|
||||||
fn is_occupied(&self, position: &P2) -> bool {
|
|
||||||
let (dist_sq, _) = self.inner.nearest_one(&position.as_arr(), &squared_euclidean).unwrap();
|
|
||||||
|
|
||||||
// Is the distance of this point to the next one less than twice the ball radius
|
|
||||||
dist_sq < 2.0 * self.ball_radius_sq
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deposit(&mut self, position: &P2) {
|
|
||||||
self.inner.add(&position.as_arr(), ()).expect("Failed to write to space")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ContinuousSticker {
|
|
||||||
/// INVARIANT: THIS SHOULD BE GREATER THAN THE BALL_RADIUS_SQ value
|
|
||||||
pub range_sq: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sticker<P2, ContinuousStorage> for ContinuousSticker {
|
|
||||||
fn should_stick<R: Rng>(&self, _rng: &mut R, space: &ContinuousStorage, position: &P2) -> bool {
|
|
||||||
let (a, _) = space.inner.nearest_one(&position.as_arr(), &squared_euclidean).unwrap();
|
|
||||||
a < self.range_sq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ContinuousWalker {
|
|
||||||
pub walk_step: f32
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Walker<P2> for ContinuousWalker {
|
|
||||||
fn walk<R: Rng>(&self, rng: &mut R, position: &P2) -> P2 {
|
|
||||||
position.clone() + P2::random_with_radius(rng, self.walk_step)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use num_integer::Roots;
|
|
||||||
use crate::system::{GriddedPosition, Position};
|
use crate::system::{GriddedPosition, Position};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ impl GriddedPosition for HexPosition {
|
|||||||
(-1, 0), (-1, 1), (0, 1),
|
(-1, 0), (-1, 1), (0, 1),
|
||||||
];
|
];
|
||||||
|
|
||||||
self.clone() + HexPosition { q: OFFSETS[neighbour_index].0, r: OFFSETS[neighbour_index].1 }
|
self.clone() + HexPosition { q: OFFSETS[neighbour_index].0, r: OFFSETS[neighbour_index].0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn linear_index(&self, grid_size: u32) -> usize {
|
fn linear_index(&self, grid_size: u32) -> usize {
|
||||||
@ -40,8 +39,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,19 +49,10 @@ 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::DIM]) -> 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 {
|
|
||||||
let q = self.q as f32;
|
|
||||||
let r = self.r as f32;
|
|
||||||
[
|
|
||||||
3f32.sqrt() * q + 3f32.sqrt() / 2f32 * r,
|
|
||||||
(3. / 2.) * r
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use anyhow::anyhow;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use crate::system::{GriddedPosition, Position, Storage};
|
use crate::system::{GriddedPosition, Position, Storage};
|
||||||
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
||||||
@ -12,17 +11,11 @@ fn taxicab_grid3(a: &[f32; 3], b: &[f32; 3]) -> f32 {
|
|||||||
(a[0] - b[0]).abs() + (a[1] - b[1]).abs() + (a[2] - b[2]).abs()
|
(a[0] - b[0]).abs() + (a[1] - b[1]).abs() + (a[2] - b[2]).abs()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KDSpace<const N: usize> {
|
pub struct KDGrid<const N: usize> {
|
||||||
pub(crate) inner: kiddo::KdTree<f32, (), N>,
|
pub(crate) inner: kiddo::KdTree<f32, (), N>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> KDSpace<N> {
|
impl Storage<Grid2D> for KDGrid<2> {
|
||||||
pub fn new() -> KDSpace<N> {
|
|
||||||
KDSpace { inner: kiddo::KdTree::new() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Storage<Grid2D> for KDSpace<2> {
|
|
||||||
fn is_occupied(&self, position: &Grid2D) -> bool {
|
fn is_occupied(&self, position: &Grid2D) -> bool {
|
||||||
let a = self.inner.best_n_within(&[position.x as f32, position.y as f32], 0f32, 1, &taxicab_grid2).unwrap();
|
let a = self.inner.best_n_within(&[position.x as f32, position.y as f32], 0f32, 1, &taxicab_grid2).unwrap();
|
||||||
!a.is_empty()
|
!a.is_empty()
|
||||||
@ -34,7 +27,7 @@ impl Storage<Grid2D> for KDSpace<2> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Storage<Grid3D> for KDSpace<3> {
|
impl Storage<Grid3D> for KDGrid<3> {
|
||||||
fn is_occupied(&self, position: &Grid3D) -> bool {
|
fn is_occupied(&self, position: &Grid3D) -> bool {
|
||||||
let a = self.inner.best_n_within(&[position.x as f32, position.y as f32, position.z as f32], 0f32, 1, &taxicab_grid3).unwrap();
|
let a = self.inner.best_n_within(&[position.x as f32, position.y as f32, position.z as f32], 0f32, 1, &taxicab_grid3).unwrap();
|
||||||
!a.is_empty()
|
!a.is_empty()
|
||||||
@ -46,22 +39,13 @@ impl Storage<Grid3D> for KDSpace<3> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KDProbabilisticSticking {
|
|
||||||
|
pub struct KDGrid2Sticker {
|
||||||
pub(crate) stick_probability: f32,
|
pub(crate) stick_probability: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KDProbabilisticSticking {
|
impl Sticker<Grid2D, KDGrid<2>> for KDGrid2Sticker {
|
||||||
fn new(stick_probability: f32) -> anyhow::Result<KDProbabilisticSticking> {
|
fn should_stick<R: Rng>(&self, rng: &mut R, space: &KDGrid<2>, position: &Grid2D) -> bool {
|
||||||
return if 0f32 < stick_probability && stick_probability <= 1f32 {
|
|
||||||
Ok(KDProbabilisticSticking { stick_probability })
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Sticking probability outside of (0, 1] range."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sticker<Grid2D, KDSpace<2>> for KDProbabilisticSticking {
|
|
||||||
fn should_stick<R: Rng>(&self, rng: &mut R, space: &KDSpace<2>, position: &Grid2D) -> bool {
|
|
||||||
let a = space.inner.best_n_within(&[position.x as f32, position.y as f32], 1f32, Grid2D::NEIGHBOURS as usize, &taxicab_grid2)
|
let a = space.inner.best_n_within(&[position.x as f32, position.y as f32], 1f32, Grid2D::NEIGHBOURS as usize, &taxicab_grid2)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -69,14 +53,14 @@ impl Sticker<Grid2D, KDSpace<2>> for KDProbabilisticSticking {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let q = 1f32 - self.stick_probability;
|
let q = (1f32 - self.stick_probability);
|
||||||
let a = q.powi(a.len() as i32);
|
let a = q.powi(a.len() as i32);
|
||||||
rng.gen_range(0f32..1f32) > a
|
rng.gen_range(0f32..1f32) > a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sticker<Grid3D, KDSpace<3>> for KDProbabilisticSticking {
|
impl Sticker<Grid3D, KDGrid<3>> for KDGrid2Sticker {
|
||||||
fn should_stick<R: Rng>(&self, rng: &mut R, space: &KDSpace<3>, position: &Grid3D) -> bool {
|
fn should_stick<R: Rng>(&self, rng: &mut R, space: &KDGrid<3>, position: &Grid3D) -> bool {
|
||||||
let a = space.inner.best_n_within(&[position.x as f32, position.y as f32, position.z as f32], 1f32, Grid2D::NEIGHBOURS as usize, &taxicab_grid3)
|
let a = space.inner.best_n_within(&[position.x as f32, position.y as f32, position.z as f32], 1f32, Grid2D::NEIGHBOURS as usize, &taxicab_grid3)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@ -5,5 +5,4 @@ pub mod square_grid;
|
|||||||
pub mod kd_grid;
|
pub mod kd_grid;
|
||||||
pub mod hexagonal;
|
pub mod hexagonal;
|
||||||
|
|
||||||
pub mod continuous_3d;
|
pub mod continuous;
|
||||||
pub mod continuous_2d;
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use num_integer::Integer;
|
|||||||
use crate::system::{GriddedPosition, Position};
|
use crate::system::{GriddedPosition, Position};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Grid2D {
|
pub struct Grid2D {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
@ -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,16 +38,12 @@ 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::DIM]) -> 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 {
|
|
||||||
[self.x as f32, self.y as f32]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GriddedPosition for Grid2D {
|
impl GriddedPosition for Grid2D {
|
||||||
@ -80,7 +76,7 @@ impl GriddedPosition for Grid2D {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Grid3D {
|
pub struct Grid3D {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
@ -102,28 +98,12 @@ impl Add for Grid3D {
|
|||||||
type Output = Grid3D;
|
type Output = Grid3D;
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
Grid3D {
|
Grid3D { x: self.x + rhs.x, y: self.y + rhs.y, z: self.z + rhs.z }
|
||||||
x: self.x + rhs.x,
|
|
||||||
y: self.y + rhs.y,
|
|
||||||
z: self.z + rhs.z,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grid3_add_test() {
|
|
||||||
assert_eq!(
|
|
||||||
Grid3D::from_cartesian([0f32, 0f32, 0f32]) + Grid3D::from_cartesian([0f32, 1f32, 0f32]),
|
|
||||||
Grid3D::from_cartesian([0f32, 1f32, 0f32])
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
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])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,38 +113,20 @@ 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::DIM]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: cartesian[0] as i32,
|
x: cartesian[0] as i32,
|
||||||
y: cartesian[1] as i32,
|
y: cartesian[1] as i32,
|
||||||
z: cartesian[2] as i32,
|
z: cartesian[2] as i32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_cartesian(&self) -> Self::Cartesian {
|
|
||||||
[self.x as f32, self.y as f32, self.z as f32]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grid3_neighbours_test() {
|
|
||||||
let neighbours = (0..Grid3D::NEIGHBOURS)
|
|
||||||
.map(|n| Grid3D::neighbour(&Grid3D::zero(), n))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
assert!(neighbours.contains(&Grid3D { x: 1, y: 0, z: 0 }));
|
|
||||||
assert!(neighbours.contains(&Grid3D { x: -1, y: 0, z: 0 }));
|
|
||||||
assert!(neighbours.contains(&Grid3D { x: 0, y: 1, z: 0 }));
|
|
||||||
assert!(neighbours.contains(&Grid3D { x: 0, y: -1, z: 0 }));
|
|
||||||
assert!(neighbours.contains(&Grid3D { x: 0, y: 0, z: 1 }));
|
|
||||||
assert!(neighbours.contains(&Grid3D { x: 0, y: 0, z: -1 }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GriddedPosition for Grid3D {
|
impl GriddedPosition for Grid3D {
|
||||||
const NEIGHBOURS: u32 = 6;
|
const NEIGHBOURS: u32 = 6;
|
||||||
|
|
||||||
fn neighbour(&self, neighbour_index: u32) -> Self {
|
fn neighbour(&self, neighbour_index: u32) -> Self {
|
||||||
let (dim, sign) = neighbour_index.div_rem(&2);
|
let (dim, sign) = neighbour_index.div_rem(&3);
|
||||||
let sign = if sign == 0 { 1 } else { -1 };
|
let sign = if sign == 0 { 1 } else { -1 };
|
||||||
let offset = Self::in_direction(dim, sign);
|
let offset = Self::in_direction(dim, sign);
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,7 @@ 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);
|
return self.backing[position.linear_index(self.grid_size)];
|
||||||
println!("{i}");
|
|
||||||
return self.backing[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deposit(&mut self, position: &P) {
|
fn deposit(&mut self, position: &P) {
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use crate::system::Position;
|
use crate::system::Position;
|
||||||
use crate::system::spaces::continuous_2d::P2;
|
use crate::system::spaces::continuous::P3;
|
||||||
use crate::system::spaces::continuous_3d::P3;
|
|
||||||
use crate::system::spaces::hexagonal::HexPosition;
|
use crate::system::spaces::hexagonal::HexPosition;
|
||||||
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
||||||
|
|
||||||
@ -50,9 +49,3 @@ impl Spawner<P3> for UniformSpawner {
|
|||||||
P3::random_with_radius(rng, radius)
|
P3::random_with_radius(rng, radius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spawner<P2> for UniformSpawner {
|
|
||||||
fn spawn<R: Rng>(&self, rng: &mut R, radius: f32) -> P2 {
|
|
||||||
P2::random_with_radius(rng, radius)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use anyhow::anyhow;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use crate::system::{GriddedPosition, Position, Storage};
|
use crate::system::{GriddedPosition, Position, Storage};
|
||||||
|
|
||||||
@ -9,17 +8,7 @@ pub trait Sticker<P: Position, S: Storage<P>> {
|
|||||||
pub struct SimpleSticking;
|
pub struct SimpleSticking;
|
||||||
|
|
||||||
pub struct ProbabilisticSticking {
|
pub struct ProbabilisticSticking {
|
||||||
pub(crate) stick_probability: f32
|
pub stick_probability: f32
|
||||||
}
|
|
||||||
|
|
||||||
impl ProbabilisticSticking {
|
|
||||||
pub fn new(stick_probability: f32) -> anyhow::Result<ProbabilisticSticking> {
|
|
||||||
return if 0f32 < stick_probability && stick_probability <= 1f32 {
|
|
||||||
Ok(ProbabilisticSticking { stick_probability })
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("Sticking probability outside of (0, 1] range."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: GriddedPosition, S: Storage<P>> Sticker<P, S> for SimpleSticking {
|
impl<P: GriddedPosition, S: Storage<P>> Sticker<P, S> for SimpleSticking {
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
use std::hash::Hash;
|
|
||||||
use bevy::render::render_resource::encase::private::RuntimeSizedArray;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use rand::distributions::Slice;
|
|
||||||
use rand::prelude::Rng;
|
use rand::prelude::Rng;
|
||||||
use crate::system::{GriddedPosition, Position};
|
use crate::system::{GriddedPosition, Position};
|
||||||
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
|
||||||
|
|
||||||
pub trait Walker<P: Position> {
|
pub trait Walker<P: Position> {
|
||||||
fn walk<R: Rng>(&self, rng: &mut R, position: &P) -> P;
|
fn walk<R: Rng>(&self, rng: &mut R, position: &P) -> P;
|
||||||
@ -18,131 +13,46 @@ impl<P: GriddedPosition> Walker<P> for LocalRandomWalker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DiagonalRandomWalker;
|
mod test {
|
||||||
|
use rand::rngs::SmallRng;
|
||||||
|
use rand::{SeedableRng, thread_rng};
|
||||||
|
use crate::system::{GriddedPosition, Position};
|
||||||
|
use crate::system::spaces::square_grid::Grid2D;
|
||||||
|
use crate::system::walker::{LocalRandomWalker, Walker};
|
||||||
|
|
||||||
impl Walker<Grid3D> for DiagonalRandomWalker {
|
#[test]
|
||||||
fn walk<R: Rng>(&self, rng: &mut R, position: &Grid3D) -> Grid3D {
|
fn uniformity() {
|
||||||
static OFFSETS: [Grid3D; 26] = [
|
let walker = LocalRandomWalker;
|
||||||
Grid3D { x: 1, y: 0, z: 0 },
|
let mut rng = SmallRng::from_rng(thread_rng()).unwrap();
|
||||||
Grid3D { x: 1, y: 1, z: 0 },
|
let mut results: Vec<Grid2D> = vec![];
|
||||||
Grid3D { x: 1, y: -1, z: 0 },
|
|
||||||
Grid3D { x: -1, y: 0, z: 0 },
|
|
||||||
Grid3D { x: -1, y: 1, z: 0 },
|
|
||||||
Grid3D { x: -1, y: -1, z: 0 },
|
|
||||||
Grid3D { x: 0, y: 1, z: 0 },
|
|
||||||
Grid3D { x: 0, y: -1, z: 0 },
|
|
||||||
Grid3D { x: 1, y: 0, z: -1 },
|
|
||||||
Grid3D { x: 1, y: 1, z: -1 },
|
|
||||||
Grid3D { x: 1, y: -1, z: -1 },
|
|
||||||
Grid3D { x: -1, y: 0, z: -1 },
|
|
||||||
Grid3D { x: -1, y: 1, z: -1 },
|
|
||||||
Grid3D { x: -1, y: -1, z: -1 },
|
|
||||||
Grid3D { x: 0, y: 1, z: -1 },
|
|
||||||
Grid3D { x: 0, y: -1, z: -1 },
|
|
||||||
Grid3D { x: 0, y: 0, z: -1 },
|
|
||||||
Grid3D { x: 1, y: 0, z: 1 },
|
|
||||||
Grid3D { x: 1, y: 1, z: 1 },
|
|
||||||
Grid3D { x: 1, y: -1, z: 1 },
|
|
||||||
Grid3D { x: -1, y: 0, z: 1 },
|
|
||||||
Grid3D { x: -1, y: 1, z: 1 },
|
|
||||||
Grid3D { x: -1, y: -1, z: 1 },
|
|
||||||
Grid3D { x: 0, y: 1, z: 1 },
|
|
||||||
Grid3D { x: 0, y: -1, z: 1 },
|
|
||||||
Grid3D { x: 0, y: 0, z: 1 },
|
|
||||||
];
|
|
||||||
|
|
||||||
position.clone() + OFFSETS[rng.gen_range(0..OFFSETS.len())].clone()
|
let origin = &Grid2D::zero();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Walker<Grid2D> for DiagonalRandomWalker {
|
let x: u32 = (1_000_000);
|
||||||
fn walk<R: Rng>(&self, rng: &mut R, position: &Grid2D) -> Grid2D {
|
for i in 0..x {
|
||||||
static OFFSETS: [Grid2D; 8] = [
|
results.push(walker.walk(&mut rng, origin));
|
||||||
Grid2D { x: 1, y: 0 },
|
|
||||||
Grid2D { x: 1, y: 1 },
|
|
||||||
Grid2D { x: 1, y: -1 },
|
|
||||||
Grid2D { x: -1, y: 0 },
|
|
||||||
Grid2D { x: -1, y: 1 },
|
|
||||||
Grid2D { x: -1, y: -1 },
|
|
||||||
Grid2D { x: 0, y: 1 },
|
|
||||||
Grid2D { x: 0, y: -1 },
|
|
||||||
];
|
|
||||||
|
|
||||||
position.clone() + OFFSETS[rng.gen_range(0..OFFSETS.len())].clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_uniformity_and_range<W: Walker<P>, P>(walker: W, expected: &[P], n: usize, tolerance: f32) where P: GriddedPosition + Hash + Eq {
|
|
||||||
use rand::thread_rng;
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
let origin = &P::zero();
|
|
||||||
|
|
||||||
let results: Vec<P> = (0..n)
|
|
||||||
.map(|_| walker.walk(&mut rng, origin))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let groups = results.iter()
|
|
||||||
.into_group_map_by(|a| (*a).clone());
|
|
||||||
|
|
||||||
assert_eq!(groups.len(), expected.len(), "Wrong number of walk positions generated");
|
|
||||||
assert!(results.iter().unique().all(|a| expected.contains(a)), "Contains unexpected walk position");
|
|
||||||
|
|
||||||
for group in groups.values() {
|
|
||||||
let proportion = group.len() as f32 / n as f32;
|
|
||||||
assert!((proportion - (1.0 / expected.len() as f32)).abs() < tolerance, "Failed tolerance check");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn uniformity_direct_grid2d() {
|
|
||||||
test_uniformity_and_range(LocalRandomWalker, &[
|
|
||||||
Grid2D { x: 1, y: 0 },
|
|
||||||
Grid2D { x: -1, y: 0 },
|
|
||||||
Grid2D { x: 0, y: 1 },
|
|
||||||
Grid2D { x: 0, y: -1 },
|
|
||||||
], 1_000_000, 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn uniformity_direct_grid3d() {
|
|
||||||
test_uniformity_and_range(LocalRandomWalker, &[
|
|
||||||
Grid3D { x: 1, y: 0, z: 0 },
|
|
||||||
Grid3D { x: -1, y: 0, z: 0 },
|
|
||||||
Grid3D { x: 0, y: 1, z: 0 },
|
|
||||||
Grid3D { x: 0, y: -1, z: 0 },
|
|
||||||
Grid3D { x: 0, y: 0, z: 1 },
|
|
||||||
Grid3D { x: 0, y: 0, z: -1 },
|
|
||||||
], 1_000_000, 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn diagonal_grid2d() {
|
|
||||||
let mut expected = Vec::new();
|
|
||||||
|
|
||||||
for x in -1..=1 {
|
|
||||||
for y in -1..=1 {
|
|
||||||
if !(x == 0 && y == 0) {
|
|
||||||
expected.push(Grid2D { x, y })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let a = results
|
||||||
|
.iter()
|
||||||
|
.filter(|a| **a == Grid2D { x: 0, y: 1 })
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let b = results
|
||||||
|
.iter()
|
||||||
|
.filter(|a| **a == Grid2D { x: 0, y: -1 })
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let c = results
|
||||||
|
.iter()
|
||||||
|
.filter(|a| **a == Grid2D { x: 1, y: 0 })
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let d = results
|
||||||
|
.iter()
|
||||||
|
.filter(|a| **a == Grid2D { x: -1, y: 0 })
|
||||||
|
.count();
|
||||||
|
|
||||||
|
println!("{} {} {} {}", a as f32 / x as f32, b as f32 / x as f32, c as f32 / x as f32, d as f32 / x as f32);
|
||||||
}
|
}
|
||||||
|
|
||||||
test_uniformity_and_range(DiagonalRandomWalker, &expected, 1_000_000, 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn diagonal_grid3d() {
|
|
||||||
let mut expected = Vec::new();
|
|
||||||
|
|
||||||
for x in -1..=1 {
|
|
||||||
for y in -1..=1 {
|
|
||||||
for z in -1..=1 {
|
|
||||||
if !(x == 0 && y == 0 && z == 0) {
|
|
||||||
expected.push(Grid3D { x, y, z })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test_uniformity_and_range(DiagonalRandomWalker, &expected, 1_000_000, 0.001);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,135 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::os::unix::fs::symlink;
|
|
||||||
use bevy::tasks::ParallelSlice;
|
|
||||||
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
|
|
||||||
use itertools::{Itertools, MinMaxResult};
|
|
||||||
use clap::Parser;
|
|
||||||
use serde::Serialize;
|
|
||||||
use crate::BoxCountCli;
|
|
||||||
use crate::cli::cli::OutputFormat;
|
|
||||||
use crate::system::{GriddedPosition, Position};
|
|
||||||
use crate::system::model::HistoryLine;
|
|
||||||
use crate::tools::read;
|
|
||||||
|
|
||||||
fn bb(data: &Vec<Grid2D>) -> ((i32, i32), (i32, i32)) {
|
|
||||||
let x = data
|
|
||||||
.iter().minmax_by(|a, b| a.x.cmp(&b.x));
|
|
||||||
|
|
||||||
let y = data
|
|
||||||
.iter().minmax_by(|a, b| a.y.cmp(&b.y));
|
|
||||||
|
|
||||||
match (x, y) {
|
|
||||||
(MinMaxResult::MinMax(min_x, max_x), MinMaxResult::MinMax(min_y, max_y)) => {
|
|
||||||
((min_x.x, min_y.y), (max_x.x, max_y.y))
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => panic!("Cannot determine bounding box")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn box_count_2d(data: &Vec<Grid2D>, box_number: u32) -> (f64, usize) {
|
|
||||||
let ((x_min, y_min), (x_max, y_max)) = bb(data);
|
|
||||||
let x_range = (x_max - x_min) as f64;
|
|
||||||
let y_range = (y_max - y_min) as f64;
|
|
||||||
|
|
||||||
let w: f64 = x_range / (box_number as f64);
|
|
||||||
|
|
||||||
let boxes_occupied = data.iter()
|
|
||||||
.map(|Grid2D { x, y }| [((x - x_min) as f64 / w) as i32, ((y - y_min) as f64 / w) as i32])
|
|
||||||
.unique()
|
|
||||||
.count();
|
|
||||||
|
|
||||||
(w, boxes_occupied)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn box_count_3d(data: &Vec<Grid3D>, size: u32) -> usize {
|
|
||||||
let n = data.len();
|
|
||||||
|
|
||||||
let x_min = data
|
|
||||||
.iter()
|
|
||||||
.min_by(|Grid3D { x: x1, .. }, Grid3D { x: x2, .. }| x1.cmp(x2))
|
|
||||||
.unwrap().x;
|
|
||||||
|
|
||||||
let x_max = data
|
|
||||||
.iter()
|
|
||||||
.max_by(|Grid3D { x: x1, .. }, Grid3D { x: x2, .. }| x1.cmp(x2))
|
|
||||||
.unwrap().x;
|
|
||||||
|
|
||||||
let y_min = data
|
|
||||||
.iter()
|
|
||||||
.min_by(|Grid3D { y: v1, .. }, Grid3D { y: v2, .. }| v1.cmp(v2))
|
|
||||||
.unwrap().y;
|
|
||||||
|
|
||||||
let y_max = data
|
|
||||||
.iter()
|
|
||||||
.max_by(|Grid3D { y: v1, .. }, Grid3D { y: v2, .. }| v1.cmp(v2))
|
|
||||||
.unwrap().y;
|
|
||||||
|
|
||||||
let z_min = data
|
|
||||||
.iter()
|
|
||||||
.min_by(|Grid3D { z: v1, .. }, Grid3D { z: v2, .. }| v1.cmp(v2))
|
|
||||||
.unwrap().y;
|
|
||||||
|
|
||||||
let z_max = data
|
|
||||||
.iter()
|
|
||||||
.max_by(|Grid3D { z: v1, .. }, Grid3D { z: v2, .. }| v1.cmp(v2))
|
|
||||||
.unwrap().y;
|
|
||||||
|
|
||||||
let x_range = (x_max - x_min) as f64;
|
|
||||||
let y_range = (y_max - y_min) as f64;
|
|
||||||
let z_range = (z_max - z_min) as f64;
|
|
||||||
|
|
||||||
let w: f64 = x_range / (size as f64);
|
|
||||||
|
|
||||||
let grid_points = data.iter()
|
|
||||||
.map(|Grid3D { x, y, z }| [
|
|
||||||
((x - x_min) as f64 / w) as i32,
|
|
||||||
((y - y_min) as f64 / w) as i32,
|
|
||||||
((z - z_min) as f64 / w) as i32,
|
|
||||||
])
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
return grid_points.iter()
|
|
||||||
.unique()
|
|
||||||
.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn box_count_nd<const N: usize>(data: &Vec<[f32; N]>, size: u32) -> usize {
|
|
||||||
let ranges = (0..N).map(|n|
|
|
||||||
match data.iter()
|
|
||||||
.minmax_by(|a, b| a[n].total_cmp(&b[n])) {
|
|
||||||
MinMaxResult::NoElements => panic!("No data"),
|
|
||||||
MinMaxResult::OneElement(_) => panic!("Needs more than one point to compute boxcount"),
|
|
||||||
MinMaxResult::MinMax(min, max) => [min[n], min[n]],
|
|
||||||
}).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let w: f32 = (ranges[0][1] - ranges[0][0]) / (size as f32);
|
|
||||||
|
|
||||||
return data.iter()
|
|
||||||
.map(|point| -> [i32; N] { std::array::from_fn(|n| ((point[n] - ranges[n][0]) / w) as i32) })
|
|
||||||
.unique()
|
|
||||||
.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct FDRow {
|
|
||||||
w: f64,
|
|
||||||
n_occupied: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn main(cli: &BoxCountCli) {
|
|
||||||
let particles: Vec<Grid2D> = read(&cli.path, cli.format);
|
|
||||||
let n_particles = dbg!(particles.len());
|
|
||||||
let box_side_counts = 1..500;
|
|
||||||
|
|
||||||
let mut writer = csv::Writer::from_path(&cli.output)
|
|
||||||
.expect("Unable to create csv");
|
|
||||||
|
|
||||||
box_side_counts
|
|
||||||
.map(|box_side_count| box_count_2d(&particles, box_side_count))
|
|
||||||
// .filter(|(w, n_occupied)| *n_occupied < n_particles) // Remove saturated values
|
|
||||||
.map(|(w, n_occupied)| FDRow { w, n_occupied })
|
|
||||||
.for_each(|row| writer.serialize(row).expect("Failed to write row"));
|
|
||||||
|
|
||||||
writer.flush().unwrap();
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::path::Path;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use crate::cli::cli::OutputFormat;
|
|
||||||
use crate::system::model::HistoryLine;
|
|
||||||
use crate::system::Position;
|
|
||||||
|
|
||||||
pub mod boxcount;
|
|
||||||
pub mod render;
|
|
||||||
|
|
||||||
pub fn read<T: Position>(path: &Path, format: OutputFormat) -> Vec<T> where T: DeserializeOwned {
|
|
||||||
match format {
|
|
||||||
OutputFormat::FullDataJson => read_json(path),
|
|
||||||
OutputFormat::Positions => read_csv(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_json<T: Position>(path: &Path) -> Vec<T> where T: DeserializeOwned {
|
|
||||||
serde_json::from_reader::<_, Vec<HistoryLine<T>>>(File::open(path).expect("Failed to open file"))
|
|
||||||
.expect("Failed to read json")
|
|
||||||
.iter()
|
|
||||||
.map(|l| (l.position.clone()))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_csv<T: Position>(path: &Path) -> Vec<T> where T: DeserializeOwned {
|
|
||||||
csv::Reader::from_path(path).expect("Failed to read positions csv").deserialize::<T>()
|
|
||||||
.collect::<Result<Vec<T>, _>>()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
#![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 serde::de::DeserializeOwned;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use svg::Node;
|
|
||||||
use svg::node::element::Rectangle;
|
|
||||||
use crate::RenderCli;
|
|
||||||
use crate::system::Position;
|
|
||||||
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, size: i32) -> Box<dyn Node>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSvg for Grid2D {
|
|
||||||
fn to_svg(&self, size: i32) -> Box<dyn Node> {
|
|
||||||
Box::new(Rectangle::new()
|
|
||||||
.set("fill", "rgb(0, 0, 0)")
|
|
||||||
.set("width", size)
|
|
||||||
.set("height", size)
|
|
||||||
.set("x", self.x * size)
|
|
||||||
.set("y", self.y * size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSvg for HexPosition {
|
|
||||||
fn to_svg(&self, size: i32) -> 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 size = size as f32;
|
|
||||||
|
|
||||||
let b = points.map(|x| [
|
|
||||||
(x[0] / 512.0) * (size),
|
|
||||||
(x[1] / 512.0) * (size)]
|
|
||||||
);
|
|
||||||
|
|
||||||
let c = b.map(|p| format!("{},{}", p[0], p[1])).join(" ");
|
|
||||||
|
|
||||||
let [x, y] = self.to_cartesian();
|
|
||||||
Box::new(Rectangle::new()
|
|
||||||
.set("fill", "rgb(0, 0, 0)")
|
|
||||||
.set("x", x * size)
|
|
||||||
.set("y", y * size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn main(args: &RenderCli) {
|
|
||||||
let positions: Vec<Grid2D> = read::<Grid2D>(&args.path, args.format);
|
|
||||||
|
|
||||||
let size: i32 = args.image_size as i32;
|
|
||||||
let max_x = positions.iter().max_by(|a, b| a.x.abs().cmp(&b.x.abs())).unwrap().x.abs();
|
|
||||||
let max_y = positions.iter().max_by(|a, b| a.y.abs().cmp(&b.y.abs())).unwrap().y.abs();
|
|
||||||
let max_size = max_x.max(max_y) * size;
|
|
||||||
|
|
||||||
let mut svg = svg::Document::new()
|
|
||||||
.set("width", max_size * size)
|
|
||||||
.set("height", max_size * size)
|
|
||||||
.set("viewBox", format!("{} {} {} {}", -max_size, -max_size, max_size * 2, max_size * 2));
|
|
||||||
|
|
||||||
svg.append(Rectangle::new()
|
|
||||||
.set("fill", "white")
|
|
||||||
.set("width", max_size * 2)
|
|
||||||
.set("height", max_size * 2)
|
|
||||||
.set("x", -max_size)
|
|
||||||
.set("y", -max_size)
|
|
||||||
);
|
|
||||||
|
|
||||||
for position in positions {
|
|
||||||
svg.append(position.to_svg(size));
|
|
||||||
}
|
|
||||||
|
|
||||||
svg::write(File::create(&args.output).unwrap(), &svg).unwrap();
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
#![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, Command, Args, Subcommand};
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use svg::Node;
|
|
||||||
use svg::node::element::Rectangle;
|
|
||||||
use crate::system::Position;
|
|
||||||
use crate::system::spaces::hexagonal::HexPosition;
|
|
||||||
|
|
||||||
mod system;
|
|
||||||
mod cli;
|
|
||||||
mod tools;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
enum ToolsCli {
|
|
||||||
Render(RenderCli),
|
|
||||||
BoxCount(BoxCountCli)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(clap::ValueEnum, Clone, Debug, Copy)]
|
|
||||||
enum Space {
|
|
||||||
Grid2D,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
|
||||||
struct RenderCli {
|
|
||||||
#[arg(value_enum, short, long, default_value_t = OutputFormat::Positions)]
|
|
||||||
format: OutputFormat,
|
|
||||||
|
|
||||||
#[arg(value_enum, short, long, default_value_t = Space::Grid2D)]
|
|
||||||
space: Space,
|
|
||||||
|
|
||||||
path: PathBuf,
|
|
||||||
output: PathBuf,
|
|
||||||
|
|
||||||
#[arg(short, long, default_value_t = 800)]
|
|
||||||
image_size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
|
||||||
struct BoxCountCli {
|
|
||||||
#[arg(value_enum, short, long, default_value_t = OutputFormat::Positions)]
|
|
||||||
format: OutputFormat,
|
|
||||||
path: PathBuf,
|
|
||||||
output: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = ToolsCli::parse();
|
|
||||||
dbg!(&args);
|
|
||||||
|
|
||||||
match args {
|
|
||||||
ToolsCli::Render(cli) => tools::render::main(&cli),
|
|
||||||
ToolsCli::BoxCount(cli) => tools::boxcount::main(&cli),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
170
src/ui.rs
170
src/ui.rs
@ -1,108 +1,94 @@
|
|||||||
//! A simplified implementation of the classic game "Breakout".
|
#![feature(array_zip)]
|
||||||
|
|
||||||
use bevy::{
|
use std::default::Default;
|
||||||
prelude::*,
|
use std::error::Error;
|
||||||
};
|
use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
|
||||||
|
use csv::{Position, Reader, ReaderBuilder};
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
// These constants are defined in `Transform` units.
|
#[derive(Parser, Resource)] // requires `derive` feature
|
||||||
// Using the default 2D camera they correspond 1:1 with screen pixels.
|
enum UICli {
|
||||||
const GAP_BETWEEN_PADDLE_AND_FLOOR: f32 = 60.0;
|
CSV(CSVArgs)
|
||||||
|
|
||||||
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)]
|
#[derive(clap::Args)]
|
||||||
struct Brick;
|
struct CSVArgs {
|
||||||
|
path: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
// Add the game's entities to our world
|
struct Position2D(i32, i32);
|
||||||
fn setup(
|
|
||||||
mut commands: Commands,
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let cli = UICli::parse();
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.insert_resource(cli)
|
||||||
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
|
window: WindowDescriptor {
|
||||||
|
title: "DLA Static 2D Render".to_string(),
|
||||||
|
width: 800.,
|
||||||
|
height: 800.,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
}))
|
||||||
|
.add_startup_system(setup_ui)
|
||||||
|
.add_startup_system(read_csv)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_csv(
|
||||||
|
cli: Res<UICli>,
|
||||||
|
mut commands: Commands
|
||||||
) {
|
) {
|
||||||
// Camera
|
let csv_path = match &cli.into_inner() {
|
||||||
commands.spawn(Camera2dBundle::default());
|
UICli::CSV(CSVArgs { path }) => path,
|
||||||
|
_ => panic!("Ahh"),
|
||||||
|
};
|
||||||
|
|
||||||
// Paddle
|
let mut reader = ReaderBuilder::new()
|
||||||
let paddle_y = BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR;
|
.has_headers(true)
|
||||||
|
.from_path(csv_path)
|
||||||
|
.expect("Failed to read csv");
|
||||||
|
|
||||||
// Bricks
|
let headers = reader.headers()
|
||||||
// Negative scales result in flipped sprites / meshes,
|
.expect("Failed to read headers");
|
||||||
// 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 x_column = headers.iter().position(|name| name.trim() == "x")
|
||||||
let bottom_edge_of_bricks = paddle_y + GAP_BETWEEN_PADDLE_AND_BRICKS;
|
.expect("Failed to find x column");
|
||||||
let total_height_of_bricks = TOP_WALL - bottom_edge_of_bricks - GAP_BETWEEN_BRICKS_AND_CEILING;
|
|
||||||
|
|
||||||
assert!(total_width_of_bricks > 0.0);
|
let y_column = headers.iter().position(|name| name.trim() == "y")
|
||||||
assert!(total_height_of_bricks > 0.0);
|
.expect("Failed to find x column");
|
||||||
|
|
||||||
// Given the space available, compute how many rows and columns of bricks we can fit
|
let positions = reader
|
||||||
let n_columns = (total_width_of_bricks / (PARTICLE_SIZE.x + GAP_BETWEEN_BRICKS)).floor() as usize;
|
.records()
|
||||||
let n_rows = (total_height_of_bricks / (PARTICLE_SIZE.y + GAP_BETWEEN_BRICKS)).floor() as usize;
|
.map(|record| {
|
||||||
let n_vertical_gaps = n_columns - 1;
|
let record = record.expect("Failed to read position");
|
||||||
|
let x: i32 = record[x_column].trim().parse::<i32>().expect("Failed to read x");
|
||||||
|
let y: i32 = record[y_column].trim().parse::<i32>().expect("Failed to read y");
|
||||||
|
|
||||||
// Because we need to round the number of columns,
|
Position2D(x, y)
|
||||||
// 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,
|
for Position2D(x, y) in positions {
|
||||||
// not its bottom-left corner
|
let rect_size = 5.0;
|
||||||
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 {
|
commands.spawn(SpriteBundle {
|
||||||
for column in 0..n_columns {
|
sprite: Sprite {
|
||||||
let brick_position = Vec2::new(
|
color: Color::rgb(0.25, 0.25, 0.75),
|
||||||
offset_x + column as f32 * (PARTICLE_SIZE.x + GAP_BETWEEN_BRICKS),
|
custom_size: Some(Vec2::new(rect_size, rect_size)),
|
||||||
offset_y + row as f32 * (PARTICLE_SIZE.y + GAP_BETWEEN_BRICKS),
|
..default()
|
||||||
);
|
},
|
||||||
|
transform: Transform::from_translation(Vec3::new((x as f32) * rect_size, (y as f32) * rect_size, 0.)),
|
||||||
// brick
|
..default()
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_ui(
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
commands.spawn(Camera2dBundle::default());
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user