Compare commits

...

6 Commits

Author SHA1 Message Date
192017d14e Add a README
Some checks failed
continuous-integration/drone/push Build is failing
2023-03-20 15:15:50 +00:00
10644adb54 Cleanup and fix accidental bevy imports 2023-03-20 15:13:45 +00:00
ded1b49068 Delete old clib items 2023-03-20 15:04:56 +00:00
3ac782fb38 Delete unneeded targets and dependencies 2023-03-20 15:04:29 +00:00
f4962e060b Re-add image size determination for easier viewing of the raw SVG 2023-03-19 18:52:56 +00:00
27529d38eb Nalg support 2023-03-19 10:26:33 +00:00
13 changed files with 178 additions and 2761 deletions

2625
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,19 +5,10 @@ edition = "2021"
# 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]]
name = "model"
path = "src/main.rs"
[[bin]]
name = "ui"
path = "src/ui.rs"
[[bin]]
name = "tools"
path = "src/tools_cli.rs"
@ -36,7 +27,6 @@ opt-level = 3
[dependencies]
clap = { version = "4.1.8", features = ["derive"] }
bevy = { version = "0.10.0" }
nd_array = "0.1.0"
num-integer = "0.1.45"
rand = { version = "0.8.5", features = ["default", "small_rng"] }
@ -44,7 +34,7 @@ csv = "1.1"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
kd-tree = { version = "0.5.1", features = ["nalgebra"] }
nalgebra = "0.32.2"
nalgebra = { version = "0.32.2", features = ["serde-serialize"] }
kiddo = "0.2.5"
anyhow = "1.0.69"
itertools = "0.10.5"

8
README.md Normal file
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.

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");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

View File

@ -1,11 +0,0 @@
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>
extern "C" {
bool dla_rust_disabled();
} // extern "C"

View File

@ -1,4 +0,0 @@
#[no_mangle]
pub extern "C" fn dla_rust_disabled() -> bool {
true
}

View File

@ -7,3 +7,4 @@ pub mod hexagonal;
pub mod continuous_3d;
pub mod continuous_2d;
pub mod nalg;

148
src/system/spaces/nalg.rs Normal file
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;
}
}

View File

@ -1,5 +1,4 @@
use std::hash::Hash;
use bevy::render::render_resource::encase::private::RuntimeSizedArray;
use itertools::Itertools;
use rand::distributions::Slice;
use rand::prelude::Rng;

View File

@ -1,6 +1,5 @@
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;

View File

@ -96,8 +96,8 @@ fn render<P: Position>(args: &RenderCli) where P: DeserializeOwned + ToSvg {
let max_size = compute_max_size(&positions) + 10.0;
let mut svg = svg::Document::new()
.set("width", max_size)
.set("height", max_size)
.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()

108
src/ui.rs
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,
));
}
}
}