mapgen
This commit is contained in:
parent
a6ec8c70b0
commit
f2ea6b328f
9 changed files with 1104 additions and 8 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -38,3 +38,5 @@ next-env.d.ts
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
/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]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
clap = { version = "4.3.0", features = ["derive"] }
|
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"
|
rand = "0.8.5"
|
||||||
sled = { version = "0.34.7", features = ["zstd", "compression"] }
|
sled = { version = "0.34.7", features = ["zstd", "compression"] }
|
||||||
specs = { version = "0.18.0", features = ["specs-derive", "uuid", "uuid_entity"] }
|
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 config;
|
||||||
|
pub mod map;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -1,5 +1,16 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use openstellaris::map::{Map, MapOptionsBuilder};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
struct Opt {}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
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(())
|
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