This commit is contained in:
Michael Zhang 2023-05-29 11:08:35 -05:00
parent a6ec8c70b0
commit f2ea6b328f
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
9 changed files with 1104 additions and 8 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

2
.gitignore vendored
View file

@ -38,3 +38,5 @@ next-env.d.ts
# Added by cargo
/target
.direnv

792
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,13 @@ edition = "2021"
[dependencies]
anyhow = "1.0.71"
clap = { version = "4.3.0", features = ["derive"] }
delaunator = "1.0.2"
derivative = "2.2.0"
derive_builder = "0.12.0"
image = "0.24.6"
imageproc = "0.23.0"
itertools = "0.10.5"
nalgebra = "0.32.2"
rand = "0.8.5"
sled = { version = "0.34.7", features = ["zstd", "compression"] }
specs = { version = "0.18.0", features = ["specs-derive", "uuid", "uuid_entity"] }

112
flake.lock Normal file
View file

@ -0,0 +1,112 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": "nixpkgs",
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1685254813,
"narHash": "sha256-Pod+U90fDJJml5cwoOvx/KKBF4HmWtK9Cttql5sfwFQ=",
"owner": "nix-community",
"repo": "fenix",
"rev": "2804d7ee704057959d831b038dea0e6845b18658",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"id": "flake-utils",
"type": "indirect"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1685168767,
"narHash": "sha256-wQgnxz0PdqbyKKpsWl/RU8T8QhJQcHfeC6lh1xRUTfk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e10802309bf9ae351eb27002c85cfdeb1be3b262",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1685325175,
"narHash": "sha256-QAKsxoePrMUoHRYpfbxBeVXDKoRJ8S864/UMjML+JiU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4295779fbaacb7158d5f32c4aa2740ef7b56d91b",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1685177170,
"narHash": "sha256-bRURsRZZmBZtQo8OHD/PRslGQC04wed6lWroQaAPSPg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "f6e3a87bf9478574f8c64ac2efec125bc19b1c64",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

34
flake.nix Normal file
View file

@ -0,0 +1,34 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
fenix.url = "github:nix-community/fenix";
};
outputs = { self, nixpkgs, flake-utils, fenix }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ fenix.overlays.default ];
};
toolchain = pkgs.fenix.stable;
in rec {
devShell = pkgs.mkShell {
packages = (with pkgs; [
cargo-deny
cargo-edit
cargo-expand
cargo-flamegraph
cargo-watch
]) ++ (with toolchain; [
cargo
rustc
clippy
# Get the nightly version of rustfmt so we can wrap comments
pkgs.fenix.default.rustfmt
]);
};
});
}

View file

@ -1,4 +1,11 @@
#[macro_use]
extern crate derive_builder;
#[macro_use]
extern crate derivative;
extern crate nalgebra as na;
pub mod config;
pub mod map;
use std::{
collections::{HashMap, HashSet},

View file

@ -1,5 +1,16 @@
use anyhow::Result;
use clap::Parser;
use openstellaris::map::{Map, MapOptionsBuilder};
#[derive(Debug, Parser)]
struct Opt {}
fn main() -> Result<()> {
let rng = rand::thread_rng();
let map_options = MapOptionsBuilder::default().build()?;
let map = Map::generate(map_options, rng);
map.dump_image("out.png")?;
Ok(())
}

146
src/map.rs Normal file
View file

@ -0,0 +1,146 @@
use std::{collections::HashSet, hash::Hash, path::Path};
use anyhow::Result;
use image::{Rgb, RgbImage};
use imageproc::{
drawing::{
draw_filled_circle_mut, draw_filled_rect, draw_filled_rect_mut, draw_line_segment_mut,
},
rect::Rect,
};
use itertools::Itertools;
use nalgebra::Vector2;
use rand::Rng;
pub struct Map {
points: Vec<Vector2<f64>>,
/// Pairs of edges (lower one first)
edges: HashSet<(usize, usize)>,
}
#[derive(Debug, Derivative, Builder)]
pub struct MapOptions {
/// Map size in world coordinate space
#[builder(default = "100")]
map_size: u32,
}
impl Map {
pub fn generate(opts: MapOptions, rand: impl Rng) -> Self {
// Generate a random array of points
// TODO: Implement a real algorithm
// TODO: Density?
// For now, let's go with a grid of points
let mut points = Vec::new();
let map_size_f: f64 = opts.map_size.into();
let cell_size = map_size_f / 200.0;
for y in -20..20 {
let pos_y = cell_size * y as f64;
for x in -20..20 {
let offset = (y % 2) as f64 * cell_size / 2.0;
let pos_x = cell_size * x as f64 + offset;
points.push(Vector2::new(pos_x, pos_y));
}
}
// Compute delaunay triangulation
let edges = {
use delaunator::{triangulate, Point, Triangulation};
let points = points
.iter()
.map(|p| Point { x: p.x, y: p.y })
.collect::<Vec<_>>();
let result = triangulate(&points);
let mut edges = HashSet::new();
for (a, b, c) in result.triangles.iter().tuples() {
edges.insert((*a.min(b), *a.max(b)));
edges.insert((*a.min(c), *a.max(c)));
edges.insert((*b.min(c), *b.max(c)));
}
edges
};
Map { points, edges }
}
pub fn dump_image(&self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
// Find the bounding rectangle of the map
let (top_left, bottom_right) = self.points.iter().fold(
(
Vector2::new(f64::INFINITY, f64::INFINITY),
Vector2::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
),
|(top_left, bottom_right), point| {
(
Vector2::new(top_left.x.min(point.x), top_left.y.min(point.y)),
Vector2::new(bottom_right.x.max(point.x), bottom_right.y.max(point.y)),
)
},
);
let space_width = bottom_right.x - top_left.x;
let space_height = top_left.y - bottom_right.y;
let image_width = 1024;
let image_height = 1024;
let background_color = Rgb([32, 108, 172]);
let line_color = Rgb([160, 160, 160]);
let circle_color = Rgb([202, 202, 202]);
let mut image = RgbImage::new(image_width, image_height);
// Draw the background
draw_filled_rect_mut(
&mut image,
Rect::at(0, 0).of_size(image_width, image_height),
background_color,
);
let to_image_space_f32 = |p: &Vector2<f64>| {
let image_x = ((p.x - top_left.x) * image_width as f64 / space_width) as f32;
let image_y =
image_height as f32 - ((p.y - bottom_right.y) * image_height as f64 / space_height) as f32;
(image_x, image_y)
};
let to_image_space_i32 = |p: &Vector2<f64>| {
let image_x = ((p.x - top_left.x) * image_width as f64 / space_width) as i32;
let image_y =
image_height as i32 - ((p.y - bottom_right.y) * image_height as f64 / space_height) as i32;
(image_x, image_y)
};
// Draw the edges
for (from_idx, to_idx) in self.edges.iter() {
println!("{from_idx} {to_idx}");
let from = self.points[*from_idx];
let to = self.points[*to_idx];
let image_from = to_image_space_f32(&from);
let image_to = to_image_space_f32(&to);
draw_line_segment_mut(&mut image, image_from, image_to, line_color);
}
// Draw the points
for point in self.points.iter() {
// Map the point to image-space
let image_point = to_image_space_i32(point);
draw_filled_circle_mut(&mut image, image_point, 2, circle_color);
}
image.save(path)?;
Ok(())
}
}