Browse Source

Minimally working voronoi map, various fixes.

Herbert Wolverson 4 years ago
parent
commit
e1fb940d3b

+ 1 - 0
book/src/chapter_28.md

@@ -158,6 +158,7 @@ Since we're re-using the exact code from Cellular Automata, we should take the c
 ```rust
 /// Searches a map, removes unreachable areas and returns the most distant tile.
 pub fn remove_unreachable_areas_returning_most_distant(map : &mut Map, start_idx : usize) -> usize {
+    map.populate_blocked();
     let map_starts : Vec<i32> = vec![start_idx as i32];
     let dijkstra_map = rltk::DijkstraMap::new(map.width, map.height, &map_starts , map, 200.0);
     let mut exit_tile = (0, 0.0f32);

+ 1 - 0
chapter-28-drunkards-walk/src/map_builders/common.rs

@@ -33,6 +33,7 @@ pub fn apply_vertical_tunnel(map : &mut Map, y1:i32, y2:i32, x:i32) {
 
 /// Searches a map, removes unreachable areas and returns the most distant tile.
 pub fn remove_unreachable_areas_returning_most_distant(map : &mut Map, start_idx : usize) -> usize {
+    map.populate_blocked();
     let map_starts : Vec<i32> = vec![start_idx as i32];
     let dijkstra_map = rltk::DijkstraMap::new(map.width, map.height, &map_starts , map, 200.0);
     let mut exit_tile = (0, 0.0f32);

+ 1 - 0
chapter-29-mazes/src/map_builders/common.rs

@@ -33,6 +33,7 @@ pub fn apply_vertical_tunnel(map : &mut Map, y1:i32, y2:i32, x:i32) {
 
 /// Searches a map, removes unreachable areas and returns the most distant tile.
 pub fn remove_unreachable_areas_returning_most_distant(map : &mut Map, start_idx : usize) -> usize {
+    map.populate_blocked();
     let map_starts : Vec<i32> = vec![start_idx as i32];
     let dijkstra_map = rltk::DijkstraMap::new(map.width, map.height, &map_starts , map, 300.0);
     let mut exit_tile = (0, 0.0f32);

+ 1 - 0
chapter-31-symmetry/src/map_builders/common.rs

@@ -36,6 +36,7 @@ pub fn apply_vertical_tunnel(map : &mut Map, y1:i32, y2:i32, x:i32) {
 
 /// Searches a map, removes unreachable areas and returns the most distant tile.
 pub fn remove_unreachable_areas_returning_most_distant(map : &mut Map, start_idx : usize) -> usize {
+    map.populate_blocked();
     let map_starts : Vec<i32> = vec![start_idx as i32];
     let dijkstra_map = rltk::DijkstraMap::new(map.width, map.height, &map_starts , map, 300.0);
     let mut exit_tile = (0, 0.0f32);

+ 2 - 1
chapter-32-voronoi/src/map_builders/common.rs

@@ -36,6 +36,7 @@ pub fn apply_vertical_tunnel(map : &mut Map, y1:i32, y2:i32, x:i32) {
 
 /// Searches a map, removes unreachable areas and returns the most distant tile.
 pub fn remove_unreachable_areas_returning_most_distant(map : &mut Map, start_idx : usize) -> usize {
+    map.populate_blocked();
     let map_starts : Vec<i32> = vec![start_idx as i32];
     let dijkstra_map = rltk::DijkstraMap::new(map.width, map.height, &map_starts , map, 300.0);
     let mut exit_tile = (0, 0.0f32);
@@ -43,7 +44,7 @@ pub fn remove_unreachable_areas_returning_most_distant(map : &mut Map, start_idx
         if *tile == TileType::Floor {
             let distance_to_start = dijkstra_map.map[i];
             // We can't get to this tile - so we'll make it a wall
-            if distance_to_start == std::f32::MAX {
+            if distance_to_start > 300.0 {
                 *tile = TileType::Wall;
             } else {
                 // If it is further away than our current exit candidate, move the exit

+ 5 - 1
chapter-32-voronoi/src/map_builders/mod.rs

@@ -15,6 +15,8 @@ mod dla;
 use dla::*;
 mod common;
 use common::*;
+mod voronoi;
+use voronoi::*;
 use specs::prelude::*;
 
 pub trait MapBuilder {
@@ -27,6 +29,7 @@ pub trait MapBuilder {
 }
 
 pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
+    /*
     let mut rng = rltk::RandomNumberGenerator::new();
     let builder = rng.roll_dice(1, 14);
     match builder {
@@ -44,6 +47,7 @@ pub fn random_builder(new_depth: i32) -> Box<dyn MapBuilder> {
         12 => Box::new(DLABuilder::central_attractor(new_depth)),
         13 => Box::new(DLABuilder::insectoid(new_depth)),
         _ => Box::new(SimpleMapBuilder::new(new_depth))
-    }
+    }*/
+    Box::new(VoronoiCellBuilder::new(new_depth))
 }
 

+ 137 - 0
chapter-32-voronoi/src/map_builders/voronoi.rs

@@ -0,0 +1,137 @@
+use super::{MapBuilder, Map,  
+    TileType, Position, spawner, SHOW_MAPGEN_VISUALIZER,
+    remove_unreachable_areas_returning_most_distant, generate_voronoi_spawn_regions,
+    draw_corridor};
+use rltk::RandomNumberGenerator;
+use specs::prelude::*;
+use std::collections::HashMap;
+
+pub struct VoronoiCellBuilder {
+    map : Map,
+    starting_position : Position,
+    depth: i32,
+    history: Vec<Map>,
+    noise_areas : HashMap<i32, Vec<usize>>,
+}
+
+impl MapBuilder for VoronoiCellBuilder {
+    fn get_map(&self) -> Map {
+        self.map.clone()
+    }
+
+    fn get_starting_position(&self) -> Position {
+        self.starting_position.clone()
+    }
+
+    fn get_snapshot_history(&self) -> Vec<Map> {
+        self.history.clone()
+    }
+
+    fn build_map(&mut self)  {
+        self.build();
+    }
+
+    fn spawn_entities(&mut self, ecs : &mut World) {
+        for area in self.noise_areas.iter() {
+            spawner::spawn_region(ecs, area.1, self.depth);
+        }
+    }
+
+    fn take_snapshot(&mut self) {
+        if SHOW_MAPGEN_VISUALIZER {
+            let mut snapshot = self.map.clone();
+            for v in snapshot.revealed_tiles.iter_mut() {
+                *v = true;
+            }
+            self.history.push(snapshot);
+        }
+    }
+}
+
+impl VoronoiCellBuilder {
+    pub fn new(new_depth : i32) -> VoronoiCellBuilder {
+        VoronoiCellBuilder{
+            map : Map::new(new_depth),
+            starting_position : Position{ x: 0, y : 0 },
+            depth : new_depth,
+            history: Vec::new(),
+            noise_areas : HashMap::new()
+        }
+    }
+
+
+    #[allow(clippy::map_entry)]
+    fn build(&mut self) {
+        let mut rng = RandomNumberGenerator::new();
+
+        // Make a Voronoi diagram. We'll do this the hard way to learn about the technique!
+        let n_seeds = 64;
+        let mut voronoi_seeds : Vec<(usize, rltk::Point)> = Vec::new();
+
+        while voronoi_seeds.len() < n_seeds {
+            let vx = rng.roll_dice(1, self.map.width-1);
+            let vy = rng.roll_dice(1, self.map.height-1);
+            let vidx = self.map.xy_idx(vx, vy);
+            let candidate = (vidx, rltk::Point::new(vx, vy));
+            if !voronoi_seeds.contains(&candidate) {
+                voronoi_seeds.push(candidate);
+            }
+        }
+
+        let mut voroni_distance = vec![(0, 0.0f32) ; n_seeds];
+        let mut voronoi_membership : Vec<i32> = vec![0 ; self.map.width as usize * self.map.height as usize];
+        for (i, vid) in voronoi_membership.iter_mut().enumerate() {
+            let x = i as i32 % self.map.width;
+            let y = i as i32 / self.map.width;
+
+            for (seed, pos) in voronoi_seeds.iter().enumerate() {
+                let distance = rltk::DistanceAlg::PythagorasSquared.distance2d(
+                    rltk::Point::new(x, y), 
+                    pos.1
+                );
+                voroni_distance[seed] = (seed, distance);
+            }
+
+            voroni_distance.sort_by(|a,b| a.1.partial_cmp(&b.1).unwrap());
+
+            *vid = voroni_distance[0].0 as i32;
+        }
+
+        for y in 1..self.map.height-1 {
+            for x in 1..self.map.width-1 {
+                let mut neighbors = 0;
+                let my_idx = self.map.xy_idx(x, y);
+                let my_seed = voronoi_membership[my_idx];
+                if voronoi_membership[self.map.xy_idx(x-1, y)] != my_seed { neighbors += 1; }
+                if voronoi_membership[self.map.xy_idx(x+1, y)] != my_seed { neighbors += 1; }
+                if voronoi_membership[self.map.xy_idx(x, y-1)] != my_seed { neighbors += 1; }
+                if voronoi_membership[self.map.xy_idx(x, y+1)] != my_seed { neighbors += 1; }
+
+                if neighbors < 2 {
+                    self.map.tiles[my_idx] = TileType::Floor;
+                }
+            }
+            self.take_snapshot();
+        }        
+
+        // Find a starting point; start at the middle and walk left until we find an open tile
+        self.starting_position = Position{ x: self.map.width / 2, y : self.map.height / 2 };
+        let mut start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
+        while self.map.tiles[start_idx] != TileType::Floor {
+            self.starting_position.x -= 1;
+            start_idx = self.map.xy_idx(self.starting_position.x, self.starting_position.y);
+        }
+        self.take_snapshot();
+
+        // Find all tiles we can reach from the starting point
+        let exit_tile = remove_unreachable_areas_returning_most_distant(&mut self.map, start_idx);
+        self.take_snapshot();
+
+        // Place the stairs
+        self.map.tiles[exit_tile] = TileType::DownStairs;
+        self.take_snapshot();
+
+        // Now we build a noise map for use in spawning entities later
+        self.noise_areas = generate_voronoi_spawn_regions(&self.map, &mut rng);
+    }    
+}