Last active
May 11, 2023 23:02
-
-
Save spookyvision/b8ea2662f34d0b1672d076b281320946 to your computer and use it in GitHub Desktop.
fractal noise w/ Dioxus
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "seedy" | |
version = "0.1.0" | |
authors = ["Anatol Ulrich <e+github@mail.taugt.net>"] | |
edition = "2021" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies] | |
dioxus = "0.3" | |
dioxus-web = "0.3" | |
log = "0.4.6" | |
# WebAssembly Debug | |
wasm-logger = "0.2.0" | |
console_error_panic_hook = "0.1.7" | |
squirrel-rng = "0.2.2" | |
getrandom = { version = "0.2.9", features = ["js"] } | |
lerp = "0.4.0" | |
gloo = { version = "0.8.0", features = ["futures"] } | |
wasm-bindgen = "0.2.85" | |
[dependencies.web-sys] | |
version = "0.3.4" | |
features = [ | |
'CanvasRenderingContext2d', | |
'Document', | |
'Element', | |
'HtmlCanvasElement', | |
'ImageData', | |
'Window', | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use dioxus::prelude::*; | |
use lerp::Lerp; | |
use wasm_bindgen::{prelude::*, Clamped}; | |
use gloo::utils::window; | |
use log::debug; | |
use squirrel_rng::{RngCore, SquirrelRng}; | |
trait NumExt { | |
fn val(&self) -> f64; | |
fn persistence(&self) -> f64; | |
} | |
impl NumExt for u32 { | |
fn val(&self) -> f64 { | |
self.to_be_bytes()[0] as f64 | |
} | |
fn persistence(&self) -> f64 { | |
(*self as f64 / 50.) + 1. | |
} | |
} | |
struct Lerp2D { | |
w: u32, | |
shrink: f64, | |
amplitude: f64, | |
rng: SquirrelRng, | |
} | |
impl Lerp2D { | |
fn new(w: u32, shrink: f64, amplitude: f64, rng: SquirrelRng) -> Self { | |
Self { | |
w, | |
shrink, | |
amplitude, | |
rng, | |
} | |
} | |
fn get_at(&mut self, x: u32, y: u32, easing: bool) -> f64 { | |
let x_scaled = x as f64 / self.shrink; | |
let y_scaled = y as f64 / self.shrink; | |
let mut t_x = x_scaled.fract(); | |
let mut t_y = y_scaled.fract(); | |
if easing { | |
t_x = simple_easing::sine_in_out(t_x as f32) as f64; | |
t_y = simple_easing::sine_in_out(t_y as f32) as f64; | |
} | |
let x1 = x_scaled as u32; | |
let y1 = y_scaled as u32; | |
let y2 = y1 + 1; | |
let offset_y1 = y1 * self.w + x1; | |
let r1 = self.rng.with_position(offset_y1).next_u32().val(); | |
let r2 = self.rng.with_position(offset_y1 + 1).next_u32().val(); | |
let offset_y2 = y2 * self.w + x1; | |
let r3 = self.rng.with_position(offset_y2).next_u32().val(); | |
let r4 = self.rng.with_position(offset_y2 + 1).next_u32().val(); | |
let r1_2 = r1.lerp(r2, t_x); | |
let r3_4 = r3.lerp(r4, t_x); | |
let r = r1_2.lerp(r3_4, t_y); | |
r * self.amplitude | |
} | |
} | |
fn main() { | |
wasm_logger::init(wasm_logger::Config::default()); | |
console_error_panic_hook::set_once(); | |
dioxus_web::launch(app); | |
} | |
fn app(cx: Scope) -> Element { | |
let octaves = use_state(cx, || 4u8); | |
let persistence_input = use_state(cx, || 50u32); | |
let seed = use_state(cx, || 0); | |
let easing = use_state(cx, || false); | |
use_effect( | |
cx, | |
(octaves, persistence_input, seed, easing), | |
|(octaves, persistence_input, seed, easing)| async move { | |
let document = window().document(); | |
let canvas = document.unwrap().get_element_by_id("pixels").unwrap(); | |
let canvas: web_sys::HtmlCanvasElement = canvas | |
.dyn_into::<web_sys::HtmlCanvasElement>() | |
.map_err(|_| ()) | |
.unwrap(); | |
let context = canvas | |
.get_context("2d") | |
.unwrap() | |
.unwrap() | |
.dyn_into::<web_sys::CanvasRenderingContext2d>() | |
.unwrap(); | |
let width = canvas | |
.get_attribute("width") | |
.map(|w| w.parse::<u32>().unwrap_or_default()) | |
.unwrap_or_default(); | |
let height = canvas | |
.get_attribute("height") | |
.map(|h| h.parse::<u32>().unwrap_or_default()) | |
.unwrap_or_default(); | |
let mut larps = vec![]; | |
let mut amplitude = 1f64; | |
let mut max = 0f64; | |
let persistence = persistence_input.persistence(); | |
for i in 0..=*octaves { | |
// sigh | |
if i > 0 { | |
amplitude *= persistence; | |
} | |
max += amplitude; | |
let larp = Lerp2D::new( | |
width, | |
2f64.powf(i as f64), | |
amplitude, | |
SquirrelRng::with_seed(*seed), | |
); | |
larps.push(larp); | |
} | |
let image_data = context | |
.create_image_data_with_sw_and_sh(width as f64, height as f64) | |
.unwrap(); | |
let mut data = image_data.data(); | |
let bytes_per_line = width * 4; | |
for y in 0..height { | |
for x in 0..width { | |
let offset = (y * bytes_per_line + 4 * x) as usize; | |
let dst_pixel = &mut data[offset..offset + 4]; | |
let val = larps | |
.iter_mut() | |
.map(|larp| larp.get_at(x, y, *easing)) | |
.reduce(|acc, e| acc + e) | |
.unwrap_or_default(); | |
let val = (val / max) as u8; | |
dst_pixel[0..=2].copy_from_slice(&[val, val, val]); | |
dst_pixel[3] = 255; | |
} | |
} | |
let image_data = web_sys::ImageData::new_with_u8_clamped_array( | |
Clamped(data.as_slice()), | |
width as u32, | |
) | |
.unwrap(); | |
context.put_image_data(&image_data, 0., 0.).unwrap(); | |
}, | |
); | |
cx.render(rsx! ( | |
div { | |
style: "text-align: center;", | |
h1 { "🥸 nosy 👃" } | |
canvas { | |
id: "pixels", | |
style: "border: 1px solid rgb(110,220,230); width: 600px; height: 300px;", | |
width: "300", | |
height: "150", | |
} | |
hr {} | |
label { | |
input { | |
r#type: "range", | |
value: "{octaves}", | |
min: "1", | |
max: "8", | |
oninput: move |ev| { | |
let val = ev.value.parse::<u8>().unwrap_or_default(); | |
octaves.set(val); | |
} | |
} | |
p { | |
format!("octaves {}", *octaves) | |
} | |
} | |
label { | |
input { | |
r#type: "range", | |
value: "{persistence_input}", | |
min: "0", | |
max: "100", | |
oninput: move |ev| { | |
let val = ev.value.parse::<u32>().unwrap_or_default(); | |
persistence_input.set(val); | |
} | |
} | |
p { | |
format!("persistence {:.2}", persistence_input.persistence()) | |
} | |
} | |
label { | |
input { | |
r#type: "range", | |
value: "{seed}", | |
min: "0", | |
max: "100", | |
oninput: move |ev| { | |
let val = ev.value.parse::<u32>().unwrap_or_default(); | |
seed.set(val); | |
} | |
} | |
p { | |
format!("seed {}", *seed) | |
} | |
} | |
label { | |
input { | |
r#type: "checkbox", | |
value: "{easing}", | |
oninput: move |ev| { | |
let val = ev.value.parse().unwrap_or_default(); | |
easing.set(val); | |
} | |
} | |
p { | |
"easing" | |
} | |
} | |
} | |
)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment