~luyu/ndwfc-rs

8d911d5e7c42638def5160f2da6fbbfe1ecb53e6 — Luyu Cheng a month ago 150cd10
feat: show generation in images
6 files changed, 460 insertions(+), 82 deletions(-)

M Cargo.lock
M Cargo.toml
A output.png
M src/main.rs
M src/tool.rs
M src/wfc.rs
M Cargo.lock => Cargo.lock +310 -0
@@ 3,12 3,123 @@
version = 3

[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"

[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"

[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "bytemuck"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"

[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"

[[package]]
name = "crc32fast"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
dependencies = [
 "cfg-if",
]

[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
 "cfg-if",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-deque"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
 "cfg-if",
 "crossbeam-epoch",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
 "cfg-if",
 "crossbeam-utils",
 "lazy_static",
 "memoffset",
 "scopeguard",
]

[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
 "cfg-if",
 "lazy_static",
]

[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
 "adler32",
 "byteorder",
]

[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"

[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 20,19 131,164 @@ dependencies = [
]

[[package]]
name = "gif"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b"
dependencies = [
 "color_quant",
 "weezl",
]

[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
 "libc",
]

[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
 "bytemuck",
 "byteorder",
 "color_quant",
 "gif",
 "jpeg-decoder",
 "num-iter",
 "num-rational",
 "num-traits",
 "png",
 "scoped_threadpool",
 "tiff",
]

[[package]]
name = "jpeg-decoder"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
dependencies = [
 "rayon",
]

[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "libc"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"

[[package]]
name = "memoffset"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
 "autocfg",
]

[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
 "adler32",
]

[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
 "adler",
 "autocfg",
]

[[package]]
name = "ndwfc-rs"
version = "0.1.0"
dependencies = [
 "image",
 "rand",
]

[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
 "autocfg",
 "num-traits",
]

[[package]]
name = "num-iter"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
dependencies = [
 "autocfg",
 "num-integer",
 "num-traits",
]

[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
 "autocfg",
 "num-integer",
 "num-traits",
]

[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
 "autocfg",
]

[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
 "hermit-abi",
 "libc",
]

[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
 "bitflags",
 "crc32fast",
 "deflate",
 "miniz_oxide 0.3.7",
]

[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 79,7 335,61 @@ dependencies = [
]

[[package]]
name = "rayon"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
dependencies = [
 "autocfg",
 "crossbeam-deque",
 "either",
 "rayon-core",
]

[[package]]
name = "rayon-core"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
 "crossbeam-channel",
 "crossbeam-deque",
 "crossbeam-utils",
 "lazy_static",
 "num_cpus",
]

[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"

[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"

[[package]]
name = "tiff"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
dependencies = [
 "jpeg-decoder",
 "miniz_oxide 0.4.4",
 "weezl",
]

[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"

[[package]]
name = "weezl"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"

M Cargo.toml => Cargo.toml +1 -0
@@ 7,3 7,4 @@ edition = "2018"

[dependencies]
rand = "0.8.4"
image = "0.23.14"

A output.png => output.png +0 -0
M src/main.rs => src/main.rs +103 -23
@@ 1,9 1,11 @@
use std::collections::HashMap;
use std::{collections::HashMap, ops::Range, rc::Rc};

use image::{GenericImage, ImageBuffer, Rgb, RgbImage};

mod tool;
mod wfc;

fn main() {
fn main() -> std::io::Result<()> {
    let tiles = vec![
        ("@@@\n@@@\n@@@", 5.0),
        ("...\n...\n...", 1.0),


@@ 15,12 17,12 @@ fn main() {
        (".=.\n.==\n.=.", 1.0),
        (".=.\n.==\n...", 1.0),
    ];
    let mut tool = tool::WaveFunctionCollapse2D::new();
    let mut tool = tool::WaveFunctionCollapse2D::new((3, 3));
    for (s, w) in tiles {
        tool.add_tile(s, w);
        tool.transform_last(tool::rotate90);
        tool.transform_last(tool::rotate180);
        tool.transform_last(tool::rotate270);
        tool.transform_last(tool::rotate90);
        tool.transform_last(tool::rotate90);
    }
    tool.print_tiles();
    tool.add_color('@', [55, 45, 45]);


@@ 29,31 31,109 @@ fn main() {
    tool.add_color('=', [125, 145, 25]);
    println!("Generate inputs for wave function collapse.");
    let (weights, rules, _) = tool.generate_input();
    for (direction, a, b) in rules.iter() {
        println!("{} and {} is adjacent in direction {}", a, b, direction);
    }
    println!("Create WFC instance.");
    let mut wfc = wfc::WaveFunctionCollapse::<2>::new(weights, rules);
    println!("Expand map to the given size.");
    let mut images = Vec::new();

    // First round with size -3..3 and -3..3.
    println!("First round.");
    wfc.expand(&[-3, -3].into(), &[3, 3].into());
    wfc.print_wavefront();
    println!("Advance a step.");
    while !wfc.step() {
        print_wave_as_map(&wfc.readout(true));
        println!("======");
        images.push(wave_to_image(&wfc.readout(true), &tool.tiles, -3..3, -3..3));
    }

    // Second round with size -6..6 and -6..6.
    println!("Second round.");
    wfc.expand(&[-6, -6].into(), &[6, 6].into());
    while !wfc.step() {
        images.push(wave_to_image(&wfc.readout(true), &tool.tiles, -6..6, -6..6));
    }
    print_wave_as_map(&wfc.wave);

    // Merge all images and save it.
    println!("Merge images.");
    merge_images(&images)
        .save("output.png")
        .expect("file should be saved");
    Ok(())
}

fn print_wave_as_map<const N: usize>(wave: &HashMap<wfc::Coordinate<N>, usize>) {
    let mut map = vec![vec![' '; 6]; 6];
fn merge_images(images: &[RgbImage]) -> RgbImage {
    let mut size = (images.len() as f64).sqrt() as usize;
    if size * size < images.len() {
        size += 1;
    }
    let width = images.last().unwrap().width() + 2;
    let height = images.last().unwrap().height() + 2;
    let mut image: RgbImage = ImageBuffer::new(size as u32 * width, size as u32 * height);
    image.fill(255);
    let mut i = 0;
    'outer: for col in 0..(size as u32) {
        for row in 0..(size as u32) {
            if i >= images.len() {
                break 'outer;
            }
            let sub_image = &images[i];
            image
                .copy_from(sub_image, col * width + 1, row * height + 1)
                .expect("should be copied");
            i += 1;
        }
    }
    image
}

fn wave_to_image<const N: usize>(
    wave: &HashMap<wfc::Coordinate<N>, usize>,
    tiles: &Vec<Rc<tool::Tile>>,
    rows: Range<isize>,
    cols: Range<isize>,
) -> RgbImage {
    let mut map = vec![vec![-1isize; cols.len()]; rows.len()];
    for (wfc::Coordinate(xs), pattern) in wave.iter() {
        let i = (xs[0] + 3) as usize;
        let j = (xs[1] + 3) as usize;
        map[i][j] = pattern.to_string().chars().next().unwrap();
    }
    println!(
        "{}",
        map.iter()
            .map(|v| v.iter().collect::<String>())
            .collect::<Vec<_>>()
            .join("\n")
    );
        let row = (xs[0] - cols.start) as usize;
        let col = (xs[1] - rows.start) as usize;
        map[row][col] = *pattern as isize;
    }
    let mut img: RgbImage = ImageBuffer::new(cols.len() as u32 * 3, rows.len() as u32 * 3);
    for row in 0..rows.len() {
        for col in 0..cols.len() {
            let pattern = map[row][col];
            if pattern < 0 {
                continue;
            }
            let tile = &tiles[pattern as usize];
            for tile_row in 0..3 {
                for tile_col in 0..3 {
                    match tile[tile_row][tile_col] {
                        '@' => img.put_pixel(
                            (col * 3 + tile_col) as u32,
                            (row * 3 + tile_row) as u32,
                            [55, 45, 45].into(),
                        ),
                        '.' => img.put_pixel(
                            (col * 3 + tile_col) as u32,
                            (row * 3 + tile_row) as u32,
                            [30, 100, 20].into(),
                        ),
                        '-' => img.put_pixel(
                            (col * 3 + tile_col) as u32,
                            (row * 3 + tile_row) as u32,
                            [180, 170, 170].into(),
                        ),
                        '=' => img.put_pixel(
                            (col * 3 + tile_col) as u32,
                            (row * 3 + tile_row) as u32,
                            [125, 145, 25].into(),
                        ),
                        _ => {}
                    }
                }
            }
        }
    }
    img
}

M src/tool.rs => src/tool.rs +11 -4
@@ 9,6 9,7 @@ pub struct Formula {
}

pub struct WaveFunctionCollapse2D {
    pub tile_shape: (usize, usize),
    pub tiles: Vec<Rc<Tile>>,
    pub colors: HashMap<char, [u8; 3]>,
    pub formulae: Vec<Rc<Formula>>,


@@ 16,8 17,9 @@ pub struct WaveFunctionCollapse2D {
}

impl WaveFunctionCollapse2D {
    pub fn new() -> WaveFunctionCollapse2D {
    pub fn new(tile_shape: (usize, usize)) -> WaveFunctionCollapse2D {
        WaveFunctionCollapse2D {
            tile_shape,
            tiles: Vec::new(),
            colors: HashMap::new(),
            formulae: Vec::new(),


@@ 58,6 60,7 @@ impl WaveFunctionCollapse2D {
        self.colors.insert(symbol, color);
    }

    #[allow(dead_code)]
    pub fn get_tile_formulae(&self) -> Vec<Rc<Formula>> {
        self.formulae.clone()
    }


@@ 100,15 103,15 @@ fn equal(m: &Vec<Vec<char>>, r: &Vec<Vec<char>>) -> bool {

fn fit_2d(d: usize, a: &Vec<Vec<char>>, b: &Vec<Vec<char>>) -> bool {
    match d {
        0 => {
        0 => a.first().unwrap() == b.last().unwrap(),
        1 => {
            for (a_row, b_row) in a.iter().zip(b.iter()) {
                if a_row.last().unwrap() != b_row.first().unwrap() {
                if a_row.first().unwrap() != b_row.last().unwrap() {
                    return false;
                }
            }
            true
        }
        1 => a.last().unwrap() == b.first().unwrap(),
        _ => true,
    }
}


@@ 123,6 126,7 @@ pub fn rotate90(tile: &Tile) -> Tile {
    rotated
}

#[allow(dead_code)]
pub fn rotate180(tile: &Tile) -> Tile {
    let mut rotated = tile.clone();
    for i in 0..tile.len() {


@@ 133,6 137,7 @@ pub fn rotate180(tile: &Tile) -> Tile {
    rotated
}

#[allow(dead_code)]
pub fn rotate270(tile: &Tile) -> Tile {
    let mut rotated = tile.clone();
    for i in 0..tile.len() {


@@ 143,6 148,7 @@ pub fn rotate270(tile: &Tile) -> Tile {
    rotated
}

#[allow(dead_code)]
pub fn flip_horizontal(tile: &Tile) -> Tile {
    let mut flipped = tile.clone();
    for i in 0..tile.len() {


@@ 153,6 159,7 @@ pub fn flip_horizontal(tile: &Tile) -> Tile {
    flipped
}

#[allow(dead_code)]
pub fn flip_vertical(tile: &Tile) -> Tile {
    let mut flipped = tile.clone();
    for i in 0..tile.len() {

M src/wfc.rs => src/wfc.rs +35 -55
@@ 103,6 103,25 @@ fn arg_max(vals: Ref<Vec<f32>>) -> Option<usize> {
        .map(|(i, _)| i)
}

fn collapse(values: Ref<Vec<f32>>, weights: &Vec<f32>) -> Option<Vec<f32>> {
    let mut rng = rand::thread_rng();
    let mut r = rng.gen_range(0.0..1.0)
        * values
            .iter()
            .zip(weights.iter())
            .map(|(x, w)| x * w)
            .sum::<f32>();
    for (i, (x, w)) in values.iter().zip(weights.iter()).enumerate() {
        r -= w * x;
        if r <= 0.0 {
            let mut ys = vec![0.0; values.len()];
            ys[i] = 1.0;
            return Some(ys);
        }
    }
    None
}

impl<const N: usize> WaveFunctionCollapse<N> {
    pub fn new(weights: Vec<f32>, rules: Vec<(usize, usize, usize)>) -> WaveFunctionCollapse<N> {
        WaveFunctionCollapse {


@@ 130,40 149,6 @@ impl<const N: usize> WaveFunctionCollapse<N> {
            .sum()
    }

    fn collapse<'a>(&self, x: Ref<Vec<f32>>) -> Option<Vec<f32>> {
        let mut rng = rand::thread_rng();
        let mut r = rng.gen_range(0.0..1.0)
            * x.iter()
                .zip(self.weights.iter())
                .map(|(x, w)| x * w)
                .sum::<f32>();
        for (i, (x, w)) in x.iter().zip(self.weights.iter()).enumerate() {
            r -= w * x;
            if r <= 0.0 {
                let mut ys = vec![0.0; self.wavefront.len()];
                ys[i] = 1.0;
                return Some(ys);
            }
        }
        None
    }

    fn neighborable(&self, dir: &Coordinate<N>, a: usize, b: usize) -> bool {
        let mut pair = (a, b);
        let mut index = dir.find_first(1);
        if index.is_none() {
            index = dir.find_first(-1);
            pair = (b, a);
        }
        let index = index.unwrap();
        for &(direction, a, b) in self.rules.iter() {
            if index == direction && pair.0 == a && pair.1 == b {
                return true;
            }
        }
        false
    }

    // Propogate changes from the given position.
    fn propagate(&mut self, p: &Coordinate<N>) {
        let mut stack = vec![p.clone()];


@@ 172,7 157,7 @@ impl<const N: usize> WaveFunctionCollapse<N> {
            for direction in create_directions::<N>() {
                let q = p + direction;
                if let Some(xs) = self.wavefront.get(&p).map(|xs| xs.clone()) {
                    if let Some(ys) = self.wavefront.get_mut(&q) {
                    if let Some(ys) = self.wavefront.get(&q) {
                        let mut modified = false;
                        for (i, y) in ys.borrow_mut().iter_mut().enumerate() {
                            if *y == 0.0 {


@@ 198,10 183,9 @@ impl<const N: usize> WaveFunctionCollapse<N> {
                        }
                    }
                } else if let Some(x) = self.wave.get(&p) {
                    if let Some(ys) = self.wavefront.get_mut(&q) {
                    if let Some(ys) = self.wavefront.get(&q) {
                        let mut modified = false;
                        let mut ys = ys.borrow_mut();
                        for (i, y) in ys.iter_mut().enumerate() {
                        for (i, y) in ys.borrow_mut().iter_mut().enumerate() {
                            if *y == 0.0 {
                                continue;
                            }


@@ 226,7 210,7 @@ impl<const N: usize> WaveFunctionCollapse<N> {
            let mut result = HashMap::new();
            // Fill up the reuslt with one-hot vectors.
            for (coordinate, weight) in self.wave.iter() {
                let mut zero = vec![0.0; self.wavefront.len()];
                let mut zero = vec![0.0; self.weights.len()];
                zero[*weight] = 1.0;
                result.insert(coordinate.clone(), RefCell::new(zero));
            }


@@ 303,25 287,21 @@ impl<const N: usize> WaveFunctionCollapse<N> {
            }
        }

        match minimum {
        if let Some((_, coordinate)) = minimum {
            // Collapse the location with minimal entropy.
            Some((_, coordinate)) => {
                match self.collapse(self.wavefront.get(&coordinate).unwrap().borrow()) {
                    Some(wave) => {
                        self.wavefront
                            .insert(coordinate.clone(), RefCell::new(wave));
                        self.propagate(&coordinate);
                    }
                    _ => println!("collapse failed"),
                }
                false
            let values = self.wavefront.get(&coordinate).unwrap().borrow();
            if let Some(wave) = collapse(values, &self.weights) {
                *self.wavefront.get(&coordinate).unwrap().borrow_mut() = wave;
                self.propagate(&coordinate);
            } else {
                println!("collapse failed at {}", coordinate);
            }
            false
        } else {
            // No minimum means all locations had been observed.
            None => {
                self.wave = self.readout(true);
                self.wavefront.clear();
                true
            }
            self.wave = self.readout(true);
            self.wavefront.clear();
            true
        }
    }