Skip to content

Add worley noise optional feature #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ mysql = { version = "20.0", optional = true }
dashmap = { version = "4.0", optional = true }
zip = { version = "0.5.8", optional = true }
rand = {version = "0.8", optional = true}

dmsort = {version = "1.0.0", optional = true}

[features]
default = ["cellularnoise", "dmi", "file", "git", "http", "json", "log", "noise", "sql", "url"]
Expand All @@ -60,6 +60,7 @@ url = ["url-dep", "percent-encoding"]
# non-default features
hash = ["base64", "const-random", "md-5", "hex", "sha-1", "sha2", "twox-hash"]
unzip = ["zip", "jobs"]
worleynoise = ["rand","dmsort"]

# internal feature-like things
jobs = ["flume"]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Additional features are:
* hash: Faster replacement for `md5`, support for SHA-1, SHA-256, and SHA-512. Requires OpenSSL on Linux.
* url: Faster replacements for `url_encode` and `url_decode`.
* unzip: Function to download a .zip from a URL and unzip it to a directory.
* worleynoise: Function that generates a type of nice looking cellular noise, more expensive than cellularnoise

## Installing

Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub mod sql;
pub mod unzip;
#[cfg(feature = "url")]
pub mod url;
#[cfg(feature = "worleynoise")]
pub mod worleynoise;

#[cfg(not(target_pointer_width = "32"))]
compile_error!("rust-g must be compiled for a 32-bit target");
229 changes: 229 additions & 0 deletions src/worleynoise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
use crate::error::Result;
use rand::*;
use std::fmt::Write;
use std::rc::Rc;

byond_fn! { worley_generate(region_size, threshold, node_per_region_chance, width, height) {
worley_noise(region_size, threshold, node_per_region_chance, width, height).ok()
} }

// This is a quite complex algorithm basically what it does is it creates 2 maps, one filled with cells and the other with 'regions' that map onto these cells.
// Each region can spawn 1 node, the cell then determines wether it is true or false depending on the distance from it to the nearest node in the region minus the second closest node.
// If this distance is greater than the threshold then the cell is true, otherwise it is false.
fn worley_noise(
str_reg_size: &str,
str_positive_threshold: &str,
str_node_per_region_chance: &str,
str_width: &str,
str_height: &str,
) -> Result<String> {
let region_size = str_reg_size.parse::<i32>()?;
let positive_threshold = str_positive_threshold.parse::<f32>()?;
let width = str_width.parse::<i32>()?;
let height = str_height.parse::<i32>()?;
let node_per_region_chance = str_node_per_region_chance.parse::<f32>()?;

//i fucking mixed up width and height again. it really doesnt matter but here is a comment just warning you.
let mut map = Map::new(region_size, height, width, node_per_region_chance);

map.generate_noise(positive_threshold as f32);

let mut output = String::new();

for row in map.cell_map {
for cell in row {
if cell.value {
let _ = write!(output, "1");
} else {
let _ = write!(output, "0");
}
}
}
Ok(output)
}

struct Map {
region_size: i32,
region_map: Vec<Vec<Rc<Region>>>,
cell_map: Vec<Vec<Cell>>,
cell_map_width: i32,
cell_map_height: i32,
node_chance: f32,
}

impl Map {
fn new(region_size: i32, cell_map_width: i32, cell_map_height: i32, node_chance: f32) -> Map {
let mut map = Map {
region_size: region_size,
region_map: Vec::new(),
cell_map: Vec::new(),
cell_map_width: cell_map_width,
cell_map_height: cell_map_height,
node_chance: node_chance,
};

map.init_regions();

for x in 0..cell_map_width {
map.cell_map.push(Vec::new());
for y in 0..cell_map_height {
let cell = Cell::new(
x,
y,
map.region_map[(x / region_size) as usize][(y / region_size) as usize].clone(),
);
map.cell_map[(x) as usize].push(cell);
}
}
map
}
fn init_regions(&mut self) {
let mut rng = rand::thread_rng();

let regions_x = self.cell_map_width / self.region_size;
let regions_y = self.cell_map_height / self.region_size;
//those two variables ensure that we dont EVER panic due to not having enough nodes spawned for the distance algorithm to function.
let mut node_count = 0;
let mut distance_in_regions_since_last_node = 0;

for i in 0..regions_x {
distance_in_regions_since_last_node += 1;
self.region_map.push(Vec::new());
for j in 0..regions_y {
distance_in_regions_since_last_node += 1;
let mut region = Region::new(i, j);
if rng.gen_range(0..100) as f32 <= self.node_chance || node_count < 2 {
let xcord = rng.gen_range(0..self.region_size);
let ycord = rng.gen_range(0..self.region_size);
let node =
Node::new(xcord + i * self.region_size, ycord + j * self.region_size);
region.node = Some(node);
node_count += 1;
distance_in_regions_since_last_node = 0;
}

if distance_in_regions_since_last_node > 3 {
node_count = 0;
}

let rcregion = Rc::new(region);

self.region_map[i as usize].push(rcregion);
}
}
}

fn get_regions_in_bound(&self, x: i32, y: i32, radius: i32) -> Vec<&Region> {
let mut regions = Vec::new();
let x_min = x - radius;
let x_max = x + radius;
let y_min = y - radius;
let y_max = y + radius;
for i in x_min..x_max {
for j in y_min..y_max {
let region_x = i;
let region_y = j;
if region_x >= 0
&& region_x < self.region_map.len() as i32
&& region_y >= 0
&& region_y < self.region_map[region_x as usize].len() as i32
{
let region = &self.region_map[region_x as usize][region_y as usize];
regions.push(region.as_ref());
}
}
}
regions
}

fn generate_noise(&mut self, threshold: f32) {
for i in 0..self.cell_map.len() {
for j in 0..self.cell_map[i as usize].len() {
let cell = &self.cell_map[i as usize][j as usize];
let region = &self.region_map[cell.region.as_ref().reg_x as usize]
[cell.region.as_ref().reg_y as usize];
let neighbours = self.get_regions_in_bound(region.reg_x, region.reg_y, 3);

let mut node_vec = Vec::new();
for neighbour in neighbours {
if neighbour.node.is_some() {
let node = neighbour.node.as_ref().unwrap();
node_vec.push(node);
}
}

dmsort::sort_by(&mut node_vec, |a, b| {
quick_distance_from_to(cell.x, cell.y, a.x, a.y)
.partial_cmp(&quick_distance_from_to(cell.x, cell.y, b.x, b.y))
.unwrap()
});
let dist = distance_from_to(cell.x, cell.y, node_vec[0].x, node_vec[0].y)
- distance_from_to(cell.x, cell.y, node_vec[1].x, node_vec[1].y);
//
let mutable_cell = &mut self.cell_map[i as usize][j as usize];
if dist.abs() > threshold {
mutable_cell.value = true;
}
}
}
}
}

fn distance_from_to(x1: i32, y1: i32, x2: i32, y2: i32) -> f32 {
let x_diff = x1 - x2;
let y_diff = y1 - y2;
let distance = (((x_diff * x_diff) + (y_diff * y_diff)) as f32).sqrt();
distance
}

fn quick_distance_from_to(x1: i32, y1: i32, x2: i32, y2: i32) -> f32 {
let x_diff = x1 - x2;
let y_diff = y1 - y2;
let distance = (x_diff.abs() + y_diff.abs()) as f32;
distance
}

struct Cell {
x: i32,
y: i32,
value: bool,
region: Rc<Region>,
}

impl Cell {
fn new(x: i32, y: i32, region: Rc<Region>) -> Cell {
Cell {
x: x,
y: y,
value: false,
region: region,
}
}
}

struct Region {
reg_x: i32,
reg_y: i32,
node: Option<Node>,
}

impl Region {
fn new(reg_x: i32, reg_y: i32) -> Region {
Region {
reg_x: reg_x,
reg_y: reg_y,
node: None,
}
}
}

struct Node {
x: i32,
y: i32,
}

impl Node {
fn new(x: i32, y: i32) -> Node {
Node { x: x, y: y }
}
}