Skip to content

Instantly share code, notes, and snippets.

@spookyvision
Last active May 11, 2023 23:02
Show Gist options
  • Save spookyvision/b8ea2662f34d0b1672d076b281320946 to your computer and use it in GitHub Desktop.
Save spookyvision/b8ea2662f34d0b1672d076b281320946 to your computer and use it in GitHub Desktop.
fractal noise w/ Dioxus
[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',
]
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