mapgen
This commit is contained in:
parent
a6ec8c70b0
commit
f2ea6b328f
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -38,3 +38,5 @@ next-env.d.ts
|
|||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
.direnv
|
||||
|
|
792
Cargo.lock
generated
792
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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
112
flake.lock
Normal 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
34
flake.nix
Normal 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
|
||||
]);
|
||||
};
|
||||
});
|
||||
}
|
|
@ -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},
|
||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -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
146
src/map.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue