├── .gitignore ├── .travis.yml ├── build.sh ├── entries ├── a-maze │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── utils.rs ├── boids │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── utils.rs ├── bouncing-ball │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── utils.rs ├── colors │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── utils.rs ├── mandelbrot │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── utils.rs └── nebula-gen │ ├── .gitignore │ ├── Cargo.toml │ └── src │ ├── lib.rs │ └── utils.rs ├── howto.html ├── index.html ├── index.js ├── package-lock.json ├── package.json ├── rust-toolchain └── template ├── bootstrap.js ├── index.html └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | built 4 | projects.json 5 | entries/**/pkg 6 | entries/**/bin 7 | entries/**/wasm-pack.log 8 | **log.txt 9 | **/wasm-pack.log 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - beta 5 | 6 | cache: 7 | directories: 8 | - $HOME/.cargo 9 | - node_modules 10 | 11 | install: 12 | # Install Node via NVM. 13 | - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash 14 | - source ~/.nvm/nvm.sh 15 | - nvm install lts/carbon 16 | # Install wasm-pack. 17 | - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f 18 | 19 | script: 20 | - ./build.sh 21 | 22 | deploy: 23 | provider: pages 24 | skip-cleanup: true 25 | github-token: $GITHUB_TOKEN 26 | local-dir: . 27 | keep-history: false 28 | on: 29 | branch: master 30 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ue 4 | 5 | cd $(dirname "$0") 6 | ROOT=$(pwd) 7 | 8 | ONE_WASM_PAGE=65536 9 | 10 | CI=${CI:-""} 11 | 12 | if test ! -d node_modules || test "$CI" != ""; then 13 | npm install 14 | fi 15 | 16 | cd ./entries 17 | 18 | JSON="[" 19 | 20 | for x in *; do 21 | if test ! -d "$x"; then 22 | continue 23 | fi 24 | 25 | echo "Building project $x" 26 | pushd "$x" > /dev/null 27 | 28 | # No cheating and trying to write custom stuff here. 29 | rm -rf ./pkg 30 | 31 | wasm-pack build > log.txt 2>&1 || { 32 | echo "Build for $x failed!" 33 | echo "=== log ===" 34 | cat log.txt 35 | exit 1 36 | } 37 | 38 | wasm_file=$(pwd)/$(ls pkg/*.wasm) 39 | js_file=$(pwd)/$(ls pkg/*.js) 40 | 41 | # Check that the wasm and JS is less than 64K! 42 | wasm_size=$(wc -c "$wasm_file" | awk '{ print $1 }') 43 | echo " size of wasm: $wasm_size" 44 | js_size=$(wc -c "$js_file" | awk '{ print $1 }') 45 | echo " size of js: $js_size" 46 | total_size=$(( $js_size + $wasm_size )) 47 | echo " total size: $total_size" 48 | if [[ "$total_size" -gt "$ONE_WASM_PAGE" ]]; then 49 | echo " Project $x is $total_size bytes -- that's bigger than $ONE_WASM_PAGE!" 50 | exit 1 51 | fi 52 | 53 | # Create the webpack page that pulls in the wasm and js. 54 | mkdir -p "../../built/$x" 55 | cd "../../built/$x/" > /dev/null 56 | 57 | cp "$ROOT/template/index.html" . 58 | cp "$ROOT/template/webpack.config.js" . 59 | cp "$ROOT/template/bootstrap.js" . 60 | cp "$wasm_file" . 61 | cp "$js_file" . 62 | 63 | # Make the bootstrap file import the correct module. 64 | underscore_x=${x//-/_} 65 | sed -i -e "s|XXX_MODULE|$underscore_x|g" bootstrap.js index.html 66 | sed -i -e "s|XXX_JS_SIZE|$js_size|g" bootstrap.js index.html 67 | sed -i -e "s|XXX_WASM_SIZE|$wasm_size|g" bootstrap.js index.html 68 | sed -i -e "s|XXX_TOTAL_SIZE|$total_size|g" bootstrap.js index.html 69 | sed -i -e "s|XXX_SOURCE|https://github.com/fitzgen/one-page-wasm/tree/master/entries/$x|g" bootstrap.js index.html 70 | 71 | # Build the bundle with webpack! 72 | "$ROOT/node_modules/.bin/webpack" --config webpack.config.js >> log.txt 2>&1 || { 73 | echo "webpack build for $x failed!" 74 | echo "=== log ===" 75 | cat log.txt 76 | exit 1 77 | } 78 | 79 | # Add the entry to the JSON. 80 | entry="{ \"name\": \"$x\", \"size\": { \"total\": $total_size, \"js\": $js_size, \"wasm\": $wasm_size } }" 81 | if [[ "$JSON" == "[" ]]; then 82 | JSON="$JSON"$'\n '"$entry" 83 | else 84 | JSON="$JSON"$',\n '"$entry" 85 | fi 86 | 87 | popd > /dev/null 88 | done 89 | 90 | JSON="$JSON"$'\n]' 91 | 92 | echo "$JSON" > "../projects.json" 93 | 94 | # Let CI deployment push built files. 95 | if test "$CI" != ""; then 96 | rm -rf "$ROOT/node_modules" 97 | git rm "$ROOT/.gitignore" 98 | fi 99 | -------------------------------------------------------------------------------- /entries/a-maze/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /entries/a-maze/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "a-maze" 3 | version = "0.1.0" 4 | authors = ["Johannes Hoff "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [features] 10 | # Uncomment to debug panics. 11 | # default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1.2" 15 | wasm-bindgen = "0.2" 16 | 17 | # The `console_error_panic_hook` crate provides better debugging of panics by 18 | # logging them with `console.error`. This is great for development, but requires 19 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 20 | # code size when deploying. 21 | console_error_panic_hook = { version = "0.1.1", optional = true } 22 | 23 | [profile.release] 24 | # Tell `rustc` to optimize for small code size. 25 | opt-level = "s" 26 | lto = true 27 | -------------------------------------------------------------------------------- /entries/a-maze/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cfg_if; 2 | extern crate wasm_bindgen; 3 | 4 | mod utils; 5 | use std::ops::{Add, Div, Mul}; 6 | use wasm_bindgen::prelude::*; 7 | 8 | #[wasm_bindgen] 9 | extern "C" { 10 | #[wasm_bindgen(js_namespace = Math)] 11 | fn random() -> f64; 12 | } 13 | 14 | const WIDTH: isize = 256; 15 | const HEIGHT: isize = 256; 16 | const GRID: isize = 15; // odd, divisible by 255 17 | const DOT_SIZE: isize = 10; // even, less than GRID 18 | const CELL_DIM: isize = WIDTH / GRID; 19 | 20 | const HEAD_COLOR: Color = Color { 21 | r: 143, 22 | g: 59, 23 | b: 27, 24 | }; 25 | const TAIL_COLOR: Color = Color { 26 | r: 185, 27 | g: 156, 28 | b: 107, 29 | }; 30 | const BORDER_COLOR: Color = Color { 31 | r: 73, 32 | g: 56, 33 | b: 41, 34 | }; 35 | const VISITED_COLOR: Color = Color { 36 | r: 189, 37 | g: 208, 38 | b: 156, 39 | }; 40 | const UNVISITED_COLOR: Color = Color { 41 | r: 102, 42 | g: 141, 43 | b: 60, 44 | }; 45 | 46 | const CONNECTED: Color = VISITED_COLOR; 47 | 48 | #[derive(PartialEq, Copy, Clone)] 49 | enum CellType { 50 | OutOfBounds, 51 | Head, 52 | Tail, 53 | Visited, 54 | Unvisited, 55 | } 56 | 57 | #[derive(PartialEq, Copy, Clone)] 58 | struct Color { 59 | r: u8, 60 | g: u8, 61 | b: u8, 62 | } 63 | 64 | #[derive(Copy, Clone)] 65 | struct Pos { 66 | x: isize, 67 | y: isize, 68 | } 69 | 70 | impl Add for Pos { 71 | type Output = Pos; 72 | 73 | fn add(self, other: Pos) -> Pos { 74 | Pos { 75 | x: self.x + other.x, 76 | y: self.y + other.y, 77 | } 78 | } 79 | } 80 | 81 | impl Add for Pos { 82 | type Output = Pos; 83 | 84 | fn add(self, other: isize) -> Pos { 85 | Pos { 86 | x: self.x + other, 87 | y: self.y + other, 88 | } 89 | } 90 | } 91 | 92 | impl Mul for Pos { 93 | type Output = Pos; 94 | 95 | fn mul(self, other: isize) -> Pos { 96 | Pos { 97 | x: self.x * other, 98 | y: self.y * other, 99 | } 100 | } 101 | } 102 | 103 | impl Div for Pos { 104 | type Output = Pos; 105 | 106 | fn div(self, other: isize) -> Pos { 107 | Pos { 108 | x: self.x / other, 109 | y: self.y / other, 110 | } 111 | } 112 | } 113 | 114 | impl Pos { 115 | fn new(x: isize, y: isize) -> Pos { 116 | Pos { x, y } 117 | } 118 | } 119 | 120 | fn set_pixel(frame_buffer: &mut [u8], pos: Pos, color: Color) { 121 | let idx = (pos.x + pos.y * WIDTH) as usize * 4; 122 | frame_buffer[idx + 0] = color.r; 123 | frame_buffer[idx + 1] = color.g; 124 | frame_buffer[idx + 2] = color.b; 125 | } 126 | 127 | fn get_pixel(frame_buffer: &[u8], pos: Pos) -> Color { 128 | let idx = (pos.x + pos.y * WIDTH) as usize * 4; 129 | Color { 130 | r: frame_buffer[idx + 0], 131 | g: frame_buffer[idx + 1], 132 | b: frame_buffer[idx + 2], 133 | } 134 | } 135 | 136 | fn full_square(frame_buffer: &mut [u8], coord: Pos, color: Color) { 137 | let corner = coord * GRID + 1; 138 | 139 | for dx in 0..(GRID - 1) { 140 | for dy in 0..(GRID - 1) { 141 | set_pixel(frame_buffer, corner + Pos::new(dx, dy), color); 142 | } 143 | } 144 | } 145 | 146 | fn head_pos(coord: Pos) -> Pos { 147 | coord * GRID + (GRID - DOT_SIZE + 1) / 2 148 | } 149 | 150 | fn middle_square(frame_buffer: &mut [u8], coord: Pos, color: Color) { 151 | let pos = head_pos(coord); 152 | for dx in 0..DOT_SIZE { 153 | for dy in 0..DOT_SIZE { 154 | set_pixel(frame_buffer, pos + Pos::new(dx, dy), color); 155 | } 156 | } 157 | } 158 | 159 | fn set_head(frame_buffer: &mut [u8], coord: Pos) { 160 | middle_square(frame_buffer, coord, HEAD_COLOR); 161 | } 162 | 163 | fn set_tail(frame_buffer: &mut [u8], coord: Pos) { 164 | middle_square(frame_buffer, coord, TAIL_COLOR); 165 | } 166 | 167 | fn init_buffer(frame_buffer: &mut [u8]) { 168 | for (y, row) in frame_buffer.chunks_mut(WIDTH as usize * 4).enumerate() { 169 | for (x, chunk) in row.chunks_mut(4).enumerate() { 170 | assert!(chunk.len() == 4); 171 | let is_edge = x as isize % GRID == 0 || y as isize % GRID == 0; 172 | let color = if is_edge { 173 | BORDER_COLOR 174 | } else { 175 | UNVISITED_COLOR 176 | }; 177 | chunk[0] = color.r; 178 | chunk[1] = color.g; 179 | chunk[2] = color.b; 180 | chunk[3] = 255; 181 | } 182 | } 183 | 184 | let mid = CELL_DIM / 2; 185 | set_head(frame_buffer, Pos::new(mid, mid)); 186 | } 187 | 188 | fn find_head(frame_buffer: &[u8]) -> Option { 189 | for y in 0..CELL_DIM { 190 | for x in 0..CELL_DIM { 191 | let coord = Pos::new(x, y); 192 | if cell_type(frame_buffer, coord) == CellType::Head { 193 | return Some(coord); 194 | } 195 | } 196 | } 197 | 198 | None 199 | } 200 | 201 | fn cell_type(frame_buffer: &[u8], coord: Pos) -> CellType { 202 | if coord.x < 0 || coord.y < 0 || coord.x >= CELL_DIM || coord.y >= CELL_DIM { 203 | return CellType::OutOfBounds; 204 | } 205 | 206 | let head_pixel = get_pixel(frame_buffer, head_pos(coord)); 207 | if head_pixel == HEAD_COLOR { 208 | return CellType::Head; 209 | } 210 | if head_pixel == TAIL_COLOR { 211 | return CellType::Tail; 212 | } 213 | if head_pixel == VISITED_COLOR { 214 | return CellType::Visited; 215 | } 216 | if head_pixel == UNVISITED_COLOR { 217 | return CellType::Unvisited; 218 | } 219 | 220 | return CellType::OutOfBounds; 221 | } 222 | 223 | // Find the middle coordinate between two neighboring cells 224 | fn mid(from: Pos, to: Pos) -> Pos { 225 | assert!((from.x - to.x).abs() + (from.y - to.y).abs() == 1); 226 | 227 | ((from + to + 1) * GRID) / 2 228 | } 229 | 230 | fn connect(frame_buffer: &mut [u8], from: Pos, to: Pos) { 231 | let dx = from.x - to.x; 232 | let dy = from.y - to.y; 233 | assert!((dx).abs() + (dy).abs() == 1); 234 | let wall_dir = Pos::new(dy.abs(), dx.abs()); 235 | 236 | let mid = mid(from, to); 237 | for i in 0..(GRID - 1) { 238 | let pos = mid + wall_dir * (i as isize - GRID / 2 + 1); 239 | set_pixel(frame_buffer, pos, CONNECTED); 240 | } 241 | } 242 | 243 | fn is_connected(frame_buffer: &[u8], from: Pos, to: Pos) -> bool { 244 | let mid = mid(from, to); 245 | get_pixel(frame_buffer, mid) != BORDER_COLOR 246 | } 247 | 248 | #[wasm_bindgen] 249 | pub fn frame(frame_buffer: &mut [u8], key_down: bool) { 250 | utils::set_panic_hook(); 251 | 252 | assert!(frame_buffer.len() == (WIDTH * HEIGHT * 4) as usize); 253 | if frame_buffer[3] != 255 || key_down { 254 | init_buffer(frame_buffer); 255 | } else { 256 | let head_coord = find_head(frame_buffer); 257 | 258 | let mut directions: [Pos; 4] = [ 259 | Pos::new(0, -1), 260 | Pos::new(-1, 0), 261 | Pos::new(0, 1), 262 | Pos::new(1, 0), 263 | ]; 264 | 265 | if let Some(head_coord) = head_coord { 266 | let mut advanced = false; 267 | // shuffle 268 | for i in 0..directions.len() { 269 | let j = (random() * (directions.len() - i) as f64) as usize; 270 | directions.swap(i, j); 271 | } 272 | 273 | for dir in directions.iter() { 274 | let new_coord = head_coord + *dir; 275 | if cell_type(frame_buffer, new_coord) == CellType::Unvisited { 276 | full_square(frame_buffer, head_coord, VISITED_COLOR); 277 | set_tail(frame_buffer, head_coord); 278 | set_head(frame_buffer, new_coord); 279 | connect(frame_buffer, head_coord, new_coord); 280 | advanced = true; 281 | break; 282 | } 283 | } 284 | if !advanced { 285 | // backtrack 286 | full_square(frame_buffer, head_coord, VISITED_COLOR); 287 | for dir in directions.iter() { 288 | let backtrack_coord = head_coord + *dir; 289 | if cell_type(frame_buffer, backtrack_coord) == CellType::Tail 290 | && is_connected(frame_buffer, head_coord, backtrack_coord) 291 | { 292 | set_head(frame_buffer, backtrack_coord); 293 | return; 294 | } 295 | } 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /entries/a-maze/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | if #[cfg(feature = "console_error_panic_hook")] { 11 | extern crate console_error_panic_hook; 12 | pub use self::console_error_panic_hook::set_once as set_panic_hook; 13 | } else { 14 | #[inline] 15 | pub fn set_panic_hook() {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /entries/boids/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /entries/boids/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "boids" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [features] 10 | # Uncomment to debug panics. 11 | # default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1.2" 15 | lazy_static = "1.1.0" 16 | wasm-bindgen = "0.2" 17 | 18 | # The `console_error_panic_hook` crate provides better debugging of panics by 19 | # logging them with `console.error`. This is great for development, but requires 20 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 21 | # code size when deploying. 22 | console_error_panic_hook = { version = "0.1.1", optional = true } 23 | 24 | [profile.release] 25 | # Tell `rustc` to optimize for small code size. 26 | opt-level = "s" 27 | lto = true 28 | -------------------------------------------------------------------------------- /entries/boids/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cfg_if; 2 | extern crate lazy_static; 3 | extern crate wasm_bindgen; 4 | 5 | mod utils; 6 | 7 | use lazy_static::lazy_static; 8 | use std::f64; 9 | use std::sync::Mutex; 10 | use wasm_bindgen::prelude::*; 11 | 12 | const NUM_BOIDS: usize = 25; 13 | 14 | struct State { 15 | flock: Vec, 16 | first_frame: bool, 17 | } 18 | 19 | lazy_static! { 20 | static ref STATE: Mutex = Mutex::new(State { 21 | flock: (0..NUM_BOIDS) 22 | .zip(0..NUM_BOIDS) 23 | .map(|(x, y)| { 24 | let x = x as f64; 25 | let y = y as f64; 26 | Boid { 27 | position: [x * 10.0 % WIDTH as f64, (y * 25.0 + 50.0) % HEIGHT as f64], 28 | direction: x * y, 29 | color: COLORS[x as usize % COLORS.len()], 30 | } 31 | }).collect(), 32 | first_frame: true, 33 | }); 34 | } 35 | 36 | #[derive(Copy, Clone)] 37 | struct Boid { 38 | position: [f64; 2], 39 | direction: f64, 40 | color: Color, 41 | } 42 | 43 | impl Boid { 44 | const RADIUS: f64 = 30.0; 45 | 46 | fn draw(&self, buf: &mut [u8]) { 47 | set_pixel( 48 | buf, 49 | self.position[0] as usize, 50 | self.position[1] as usize, 51 | self.color, 52 | ); 53 | } 54 | 55 | fn next(&self, me: usize, flock: &[Boid]) -> Boid { 56 | let mut closest = None; 57 | 58 | let mut sum_dir = self.direction; 59 | let mut sum_pos = self.position; 60 | let mut num_near = 1_u32; 61 | 62 | flock 63 | .iter() 64 | .enumerate() 65 | .filter(|(i, _)| *i != me) 66 | .for_each(|(_, b)| { 67 | let b_dist_sq = self.distance_squared(b); 68 | 69 | if b_dist_sq <= Self::RADIUS * Self::RADIUS { 70 | num_near += 1; 71 | sum_dir += b.direction; 72 | sum_pos[0] += b.position[0]; 73 | sum_pos[1] += b.position[1]; 74 | } 75 | 76 | if b_dist_sq > 50.0 { 77 | return; 78 | } 79 | 80 | closest = Some(closest.map_or(b, |c| { 81 | if b_dist_sq < self.distance_squared(c) { 82 | b 83 | } else { 84 | c 85 | } 86 | })); 87 | }); 88 | 89 | let avg_pos = [sum_pos[0] / num_near as f64, sum_pos[1] / num_near as f64]; 90 | let avg_dir = sum_dir / num_near as f64; 91 | 92 | let mut next = self.clone(); 93 | 94 | let velocity = [self.direction.sin(), self.direction.cos()]; 95 | let left = [-velocity[1], velocity[0]]; 96 | 97 | if num_near > 1 { 98 | let delta = [avg_dir.sin(), avg_dir.cos()]; 99 | let left_right = left[0] * delta[0] + left[1] * delta[1]; 100 | next.direction = if left_right > 0.0 { 101 | next.direction - 0.25 * left_right 102 | } else { 103 | next.direction - 0.25 * left_right 104 | }; 105 | 106 | let delta = [avg_pos[0] - self.position[0], avg_pos[1] - self.position[1]]; 107 | let left_right = left[0] * delta[0] + left[1] * delta[1]; 108 | next.direction = if left_right > 0.0 { 109 | next.direction - 0.02 * left_right 110 | } else { 111 | next.direction - 0.02 * left_right 112 | }; 113 | } 114 | 115 | if let Some(closest) = closest { 116 | let delta = [ 117 | closest.position[0] - self.position[0], 118 | closest.position[1] - self.position[1], 119 | ]; 120 | let left_right = left[0] * delta[0] + left[1] * delta[1]; 121 | next.direction = if left_right > 0.0 { 122 | next.direction + 0.2 123 | } else { 124 | next.direction - 0.2 125 | }; 126 | } 127 | 128 | next.position = [ 129 | (self.position[0] + velocity[0] + WIDTH as f64) % WIDTH as f64, 130 | (self.position[1] + velocity[1] + HEIGHT as f64) % HEIGHT as f64, 131 | ]; 132 | next 133 | } 134 | 135 | fn distance_squared(&self, b: &Boid) -> f64 { 136 | let dx = self.position[0] - b.position[0]; 137 | let dy = self.position[1] - b.position[1]; 138 | dx * dx + dy * dy 139 | } 140 | } 141 | 142 | #[derive(Copy, Clone)] 143 | struct Color { 144 | r: u8, 145 | g: u8, 146 | b: u8, 147 | a: u8, 148 | } 149 | 150 | const COLORS: &[Color] = &[ 151 | Color { 152 | r: 200, 153 | g: 60, 154 | b: 80, 155 | a: 255, 156 | }, 157 | Color { 158 | r: 30, 159 | g: 80, 160 | b: 90, 161 | a: 255, 162 | }, 163 | Color { 164 | r: 70, 165 | g: 149, 166 | b: 100, 167 | a: 255, 168 | }, 169 | Color { 170 | r: 12, 171 | g: 120, 172 | b: 200, 173 | a: 255, 174 | }, 175 | Color { 176 | r: 10, 177 | g: 80, 178 | b: 75, 179 | a: 255, 180 | }, 181 | Color { 182 | r: 80, 183 | g: 10, 184 | b: 65, 185 | a: 255, 186 | }, 187 | Color { 188 | r: 90, 189 | g: 65, 190 | b: 5, 191 | a: 255, 192 | }, 193 | Color { 194 | r: 230, 195 | g: 10, 196 | b: 10, 197 | a: 255, 198 | }, 199 | Color { 200 | r: 10, 201 | g: 230, 202 | b: 10, 203 | a: 255, 204 | }, 205 | Color { 206 | r: 10, 207 | g: 10, 208 | b: 230, 209 | a: 255, 210 | }, 211 | ]; 212 | 213 | const WIDTH: usize = 256; 214 | const HEIGHT: usize = 256; 215 | 216 | fn set_pixel(buf: &mut [u8], x: usize, y: usize, color: Color) { 217 | assert!(buf.len() as usize == WIDTH * HEIGHT * 4); 218 | if x >= WIDTH || y >= HEIGHT { 219 | return; 220 | } 221 | let a = (color.a as f64) / 255.0; 222 | let idx = x * 4 + y * WIDTH * 4; 223 | buf[idx + 0] = (buf[idx + 0] as f64 * (1.0 - a) + color.r as f64 * a).ceil() as u8; 224 | buf[idx + 1] = (buf[idx + 1] as f64 * (1.0 - a) + color.g as f64 * a).ceil() as u8; 225 | buf[idx + 2] = (buf[idx + 2] as f64 * (1.0 - a) + color.b as f64 * a).ceil() as u8; 226 | buf[idx + 3] = 255; 227 | } 228 | 229 | #[wasm_bindgen] 230 | pub fn frame(frame_buffer: &mut [u8], key_down: bool) { 231 | utils::set_panic_hook(); 232 | 233 | let mut state = STATE.lock().unwrap(); 234 | 235 | let bg_alpha = if state.first_frame { 255 } else { 40 }; 236 | for y in 0..WIDTH { 237 | for x in 0..HEIGHT { 238 | set_pixel( 239 | frame_buffer, 240 | x, 241 | y, 242 | Color { 243 | r: 255, 244 | g: 255, 245 | b: 255, 246 | a: bg_alpha, 247 | }, 248 | ); 249 | } 250 | } 251 | 252 | if key_down { 253 | let n = state.flock.len(); 254 | state.flock.push(Boid { 255 | position: [128.0, 128.0], 256 | direction: 0.0, 257 | color: COLORS[n % COLORS.len()], 258 | }); 259 | } 260 | 261 | let new_flock: Vec<_> = state 262 | .flock 263 | .iter() 264 | .enumerate() 265 | .map(|(i, b)| { 266 | b.draw(frame_buffer); 267 | b.next(i, &state.flock) 268 | }).collect(); 269 | 270 | state.flock = new_flock; 271 | state.first_frame = false; 272 | } 273 | -------------------------------------------------------------------------------- /entries/boids/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | if #[cfg(feature = "console_error_panic_hook")] { 11 | extern crate console_error_panic_hook; 12 | pub use self::console_error_panic_hook::set_once as set_panic_hook; 13 | } else { 14 | #[inline] 15 | pub fn set_panic_hook() {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /entries/bouncing-ball/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /entries/bouncing-ball/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bouncing-ball" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [features] 10 | # Uncomment to debug panics. 11 | # default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1.2" 15 | wasm-bindgen = "0.2" 16 | 17 | # The `console_error_panic_hook` crate provides better debugging of panics by 18 | # logging them with `console.error`. This is great for development, but requires 19 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 20 | # code size when deploying. 21 | console_error_panic_hook = { version = "0.1.1", optional = true } 22 | 23 | [profile.release] 24 | # Tell `rustc` to optimize for small code size. 25 | opt-level = "s" 26 | lto = true 27 | -------------------------------------------------------------------------------- /entries/bouncing-ball/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cfg_if; 2 | extern crate wasm_bindgen; 3 | 4 | mod utils; 5 | 6 | use std::mem; 7 | use wasm_bindgen::prelude::*; 8 | 9 | #[derive(Copy, Clone, Debug)] 10 | struct Color { 11 | r: u8, 12 | g: u8, 13 | b: u8, 14 | a: u8, 15 | } 16 | 17 | impl Color { 18 | fn rotate(&mut self) { 19 | let b = self.b; 20 | self.b = self.g; 21 | self.g = self.r; 22 | self.r = b; 23 | } 24 | } 25 | 26 | const WIDTH: usize = 256; 27 | const HEIGHT: usize = 256; 28 | 29 | fn set_pixel(buf: &mut [u8], x: usize, y: usize, color: Color) { 30 | assert!(buf.len() as usize == WIDTH * HEIGHT * 4); 31 | buf[x * 4 + y * WIDTH * 4 + 0] = color.r; 32 | buf[x * 4 + y * WIDTH * 4 + 1] = color.g; 33 | buf[x * 4 + y * WIDTH * 4 + 2] = color.b; 34 | buf[x * 4 + y * WIDTH * 4 + 3] = color.a; 35 | } 36 | 37 | struct Ball { 38 | position: [isize; 2], 39 | velocity: [isize; 2], 40 | radius: isize, 41 | } 42 | 43 | impl Ball { 44 | fn draw(&self, buf: &mut [u8], color: Color) { 45 | for dy in -self.radius..self.radius { 46 | let r = self.radius as f64; 47 | let width = ((r * r) - (dy as f64 * dy as f64)).sqrt().round() as isize; 48 | for dx in -width..width { 49 | let x = self.position[0] + dx; 50 | let y = self.position[1] + dy; 51 | set_pixel(buf, x as usize, y as usize, color); 52 | } 53 | } 54 | } 55 | 56 | fn update(&mut self) -> bool { 57 | self.position = [ 58 | self.position[0] + self.velocity[0], 59 | self.position[1] + self.velocity[1], 60 | ]; 61 | 62 | let mut hit = false; 63 | 64 | if self.position[0] - self.radius <= 0 || self.position[0] + self.radius >= WIDTH as isize { 65 | self.velocity[0] = -self.velocity[0]; 66 | self.position[0] += self.velocity[0]; 67 | hit = true; 68 | } 69 | 70 | if self.position[1] - self.radius <= 0 || self.position[1] + self.radius >= HEIGHT as isize 71 | { 72 | self.velocity[1] = -self.velocity[1]; 73 | self.position[1] += self.velocity[1]; 74 | hit = true; 75 | } 76 | 77 | hit 78 | } 79 | } 80 | 81 | static mut BALL: Ball = Ball { 82 | position: [111, 37], 83 | velocity: [5, 3], 84 | radius: 10, 85 | }; 86 | 87 | static mut BALL_COLOR: Color = Color { 88 | r: 10, 89 | g: 20, 90 | b: 175, 91 | a: 255, 92 | }; 93 | 94 | static mut BG_COLOR: Color = Color { 95 | r: 240, 96 | g: 200, 97 | b: 70, 98 | a: 255, 99 | }; 100 | 101 | #[wasm_bindgen] 102 | pub fn frame(frame_buffer: &mut [u8], key_down: bool) { 103 | utils::set_panic_hook(); 104 | 105 | for y in 0..WIDTH { 106 | for x in 0..HEIGHT { 107 | set_pixel(frame_buffer, x, y, unsafe { BG_COLOR }); 108 | } 109 | } 110 | 111 | if key_down { 112 | unsafe { 113 | let tmp = BALL.velocity[0]; 114 | BALL.velocity[0] = BALL.velocity[1]; 115 | BALL.velocity[1] = -tmp; 116 | } 117 | } 118 | 119 | unsafe { 120 | BALL.draw(frame_buffer, unsafe { BALL_COLOR }); 121 | if BALL.update() { 122 | BALL_COLOR.rotate(); 123 | BG_COLOR.rotate(); 124 | mem::swap(&mut BALL_COLOR, &mut BG_COLOR); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /entries/bouncing-ball/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | if #[cfg(feature = "console_error_panic_hook")] { 11 | extern crate console_error_panic_hook; 12 | pub use self::console_error_panic_hook::set_once as set_panic_hook; 13 | } else { 14 | #[inline] 15 | pub fn set_panic_hook() {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /entries/colors/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /entries/colors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "colors" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [features] 10 | # Uncomment to debug panics. 11 | # default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1.2" 15 | wasm-bindgen = "0.2" 16 | 17 | # The `console_error_panic_hook` crate provides better debugging of panics by 18 | # logging them with `console.error`. This is great for development, but requires 19 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 20 | # code size when deploying. 21 | console_error_panic_hook = { version = "0.1.1", optional = true } 22 | 23 | [profile.release] 24 | # Tell `rustc` to optimize for small code size. 25 | opt-level = "s" 26 | lto = true 27 | -------------------------------------------------------------------------------- /entries/colors/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cfg_if; 2 | extern crate wasm_bindgen; 3 | 4 | mod utils; 5 | use wasm_bindgen::prelude::*; 6 | 7 | static mut TIME: u32 = 0; 8 | 9 | #[wasm_bindgen] 10 | pub fn frame(frame_buffer: &mut [u8], _key_down: bool) { 11 | utils::set_panic_hook(); 12 | 13 | let time = unsafe { 14 | let t = TIME; 15 | TIME += 1; 16 | t 17 | }; 18 | 19 | for (y, row) in frame_buffer.chunks_mut(256 * 4).enumerate() { 20 | for (x, chunk) in row.chunks_mut(4).enumerate() { 21 | assert!(chunk.len() == 4); 22 | let r = ( ( f64::from(time) / 100.0 ).sin() * 128.0 + 128.0 ) as u8; 23 | let g = ( ( f64::from(time) / 10.0 ).cos() * 128.0 + 128.0 ) as u8; 24 | let b = ( ( f64::from(time + x as u32 + y as u32) / 50.0 ).cos() * 128.0 + 128.0 ) as u8; 25 | chunk[0] = r; 26 | chunk[1] = g; 27 | chunk[2] = b; 28 | chunk[3] = 255; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /entries/colors/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | if #[cfg(feature = "console_error_panic_hook")] { 11 | extern crate console_error_panic_hook; 12 | pub use self::console_error_panic_hook::set_once as set_panic_hook; 13 | } else { 14 | #[inline] 15 | pub fn set_panic_hook() {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /entries/mandelbrot/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /entries/mandelbrot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mandelbrot" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [features] 10 | # Uncomment to debug panics. 11 | # default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1.2" 15 | wasm-bindgen = "0.2" 16 | 17 | # The `console_error_panic_hook` crate provides better debugging of panics by 18 | # logging them with `console.error`. This is great for development, but requires 19 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 20 | # code size when deploying. 21 | console_error_panic_hook = { version = "0.1.1", optional = true } 22 | hsl = "0.1.1" 23 | 24 | [profile.release] 25 | # Tell `rustc` to optimize for small code size. 26 | opt-level = "s" 27 | lto = true 28 | -------------------------------------------------------------------------------- /entries/mandelbrot/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cfg_if; 2 | extern crate hsl; 3 | extern crate wasm_bindgen; 4 | 5 | mod utils; 6 | use wasm_bindgen::prelude::*; 7 | 8 | const WIDTH: usize = 256; 9 | const HEIGHT: usize = 256; 10 | const MAX_ITER: u8 = 255; 11 | 12 | static mut TIME: usize = 0; 13 | static mut MANDELBROT: Option> = None; 14 | static mut VIEWPORT: f64 = 0.5; 15 | static mut OFFSET_X: f64 = -0.29; 16 | static mut OFFSET_Y: f64 = -1.05; 17 | 18 | fn generate_mandelbrot(viewport: f64, offset_x: f64, offset_y: f64) -> Vec { 19 | let mut mandelbrot = Vec::with_capacity(WIDTH * HEIGHT); 20 | for y in 0..HEIGHT { 21 | for x in 0..WIDTH { 22 | let x0 = (x as f64) / (WIDTH as f64) * viewport + offset_x; 23 | let y0 = (y as f64) / (HEIGHT as f64) * viewport + offset_y; 24 | let mut x = 0.0; 25 | let mut y = 0.0; 26 | let mut iter = 0; 27 | while iter < MAX_ITER { 28 | if x * x + y * y > 2.0 * 2.0 { 29 | break; 30 | } 31 | let xtemp = x * x - y * y + x0; 32 | y = 2.0 * x * y + y0; 33 | x = xtemp; 34 | iter += 1; 35 | } 36 | mandelbrot.push(iter); 37 | } 38 | } 39 | mandelbrot 40 | } 41 | 42 | #[wasm_bindgen] 43 | pub fn frame(frame_buffer: &mut [u8], key_down: bool) { 44 | utils::set_panic_hook(); 45 | 46 | let time = unsafe { 47 | let t = TIME; 48 | TIME += 1; 49 | t 50 | }; 51 | 52 | let mut mandelbrot = unsafe { MANDELBROT.take() }; 53 | 54 | if key_down { 55 | unsafe { 56 | VIEWPORT /= 2.0; 57 | OFFSET_X += VIEWPORT / 2.0; 58 | OFFSET_Y += VIEWPORT / 2.0; 59 | } 60 | mandelbrot = None; 61 | } 62 | 63 | let mandelbrot = 64 | mandelbrot.unwrap_or_else(|| unsafe { generate_mandelbrot(VIEWPORT, OFFSET_X, OFFSET_Y) }); 65 | 66 | for (pixel, iter) in frame_buffer.chunks_mut(4).zip(mandelbrot.iter()) { 67 | let color = hsl::HSL { 68 | h: ((((*iter as usize * 20 - time) % 360) + 360) % 360) as f64, 69 | s: 0.7, 70 | l: 0.7, 71 | }.to_rgb(); 72 | 73 | pixel[0] = color.0; 74 | pixel[1] = color.1; 75 | pixel[2] = color.2; 76 | pixel[3] = 255; 77 | } 78 | 79 | unsafe { 80 | MANDELBROT = Some(mandelbrot); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /entries/mandelbrot/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | if #[cfg(feature = "console_error_panic_hook")] { 11 | extern crate console_error_panic_hook; 12 | pub use self::console_error_panic_hook::set_once as set_panic_hook; 13 | } else { 14 | #[inline] 15 | pub fn set_panic_hook() {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /entries/nebula-gen/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /entries/nebula-gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nebula-gen" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [features] 10 | # Uncomment to debug panics. 11 | # default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | cfg-if = "0.1.2" 15 | lazy_static = "1.1.0" 16 | wasm-bindgen = "0.2" 17 | 18 | # The `console_error_panic_hook` crate provides better debugging of panics by 19 | # logging them with `console.error`. This is great for development, but requires 20 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 21 | # code size when deploying. 22 | console_error_panic_hook = { version = "0.1.1", optional = true } 23 | js-sys = "0.3.2" 24 | 25 | [profile.release] 26 | # Tell `rustc` to optimize for small code size. 27 | opt-level = "s" 28 | lto = true 29 | -------------------------------------------------------------------------------- /entries/nebula-gen/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cfg_if; 2 | extern crate js_sys; 3 | extern crate lazy_static; 4 | extern crate wasm_bindgen; 5 | 6 | mod utils; 7 | 8 | use lazy_static::lazy_static; 9 | use std::sync::Mutex; 10 | use wasm_bindgen::prelude::*; 11 | 12 | const WIDTH: usize = 256; 13 | const HEIGHT: usize = 256; 14 | const NUM_CELLS: usize = 64; 15 | const MAX_VELOCITY: f64 = 1.25; 16 | 17 | // Distance between two points. 18 | fn distance(a: [f64; 2], b: [f64; 2]) -> f64 { 19 | ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt() 20 | } 21 | 22 | // Make a vector field from cellular noise. Generate some random center points 23 | // for the cells. Then for each pixel, find its closest cell center point. Set 24 | // the corresponding entry in the vector field to the x and y distance between 25 | // that cell center point and the pixel. 26 | fn make_distance_vector_field() -> Vec<[f64; 2]> { 27 | let cell_points: Vec<_> = (0..NUM_CELLS).map(|_| random_pos()).collect(); 28 | let mut field = vec![[0.0, 0.0]; WIDTH * HEIGHT]; 29 | 30 | for y in 0..HEIGHT { 31 | for x in 0..WIDTH { 32 | let this_pos = [x as f64, y as f64]; 33 | 34 | let mut closest = cell_points[0]; 35 | let mut closest_distance = distance(this_pos, closest); 36 | 37 | for p in cell_points.iter().cloned() { 38 | let d = distance(this_pos, p); 39 | if d < closest_distance { 40 | closest = p; 41 | closest_distance = d; 42 | } 43 | } 44 | 45 | field[x + y * WIDTH] = [this_pos[0] - closest[0], this_pos[1] - closest[1]]; 46 | } 47 | } 48 | 49 | field 50 | } 51 | 52 | struct Point { 53 | pos: [f64; 2], 54 | vel: [f64; 2], 55 | color: [u8; 3], 56 | } 57 | 58 | fn random_color() -> [u8; 3] { 59 | let mut r = js_sys::Math::random(); 60 | let mut g = js_sys::Math::random(); 61 | let mut b = js_sys::Math::random(); 62 | match js_sys::Math::random() { 63 | x if x < 1.0 / 3.0 => { 64 | r += (1.0 - r) / 2.0; 65 | } 66 | x if x < 2.0 / 3.0 => { 67 | g = (1.0 - g) / 2.0; 68 | } 69 | _ => { 70 | b = (1.0 - b) / 2.0; 71 | } 72 | } 73 | [ 74 | (r * 2.5).round() as u8, 75 | (g * 1.9).round() as u8, 76 | (b * 2.8).round() as u8, 77 | ] 78 | } 79 | 80 | fn random_pos() -> [f64; 2] { 81 | [ 82 | js_sys::Math::random() * 255.0, 83 | js_sys::Math::random() * 255.0, 84 | ] 85 | } 86 | 87 | fn random_vel() -> [f64; 2] { 88 | [ 89 | js_sys::Math::random() * MAX_VELOCITY, 90 | js_sys::Math::random() * MAX_VELOCITY, 91 | ] 92 | } 93 | 94 | lazy_static! { 95 | static ref VEC_FIELD: Mutex> = Mutex::new(vec![]); 96 | static ref POINTS: Mutex> = Mutex::new( 97 | (0..WIDTH * HEIGHT / 16) 98 | .map(|_| Point { 99 | pos: random_pos(), 100 | vel: random_vel(), 101 | color: random_color(), 102 | }).collect() 103 | ); 104 | } 105 | 106 | fn clamp_vel(v: f64) -> f64 { 107 | match v { 108 | v if v > MAX_VELOCITY => MAX_VELOCITY, 109 | v if v < -MAX_VELOCITY => -MAX_VELOCITY, 110 | v => v, 111 | } 112 | } 113 | 114 | #[wasm_bindgen] 115 | pub fn frame(frame_buffer: &mut [u8], key_down: bool) { 116 | utils::set_panic_hook(); 117 | 118 | let mut vec_field = VEC_FIELD.lock().unwrap(); 119 | if key_down { 120 | vec_field.clear(); 121 | } 122 | if vec_field.is_empty() { 123 | vec_field.extend(make_distance_vector_field()); 124 | 125 | for (pixel, [x, y]) in frame_buffer.chunks_mut(4).zip(vec_field.iter().cloned()) { 126 | // The furthest distance possible is about 362 if the pixel is in 127 | // the opposite corner from every cell center point. Halve that 128 | // range since we are taking absolute value. 129 | let x = x.abs() / 181.0; 130 | let y = y.abs() / 181.0; 131 | let distance = (x.powi(2) + y.powi(2)).sqrt(); 132 | let delta_xy = (x - y).abs(); 133 | 134 | let x = x * 255.0; 135 | let y = y * 255.0; 136 | let distance = distance * 255.0; 137 | 138 | pixel[0] = (distance + 2.0 * x * delta_xy) as u8; 139 | pixel[1] = (distance / 4.0) as u8; 140 | pixel[2] = (distance + 2.0 * y * delta_xy) as u8; 141 | pixel[3] = 255; 142 | } 143 | } 144 | 145 | let mut points = POINTS.lock().unwrap(); 146 | for p in points.iter_mut() { 147 | let x = p.pos[0].round() as usize % WIDTH; 148 | let y = p.pos[1].round() as usize % HEIGHT; 149 | 150 | let v = vec_field[x + y * WIDTH]; 151 | 152 | // Don't let points get stuck on the outer edges, which they otherwise 153 | // tend to do. 154 | if x == 0 || x == 255 { 155 | p.pos[0] = js_sys::Math::random() * 255.0; 156 | } 157 | if y == 0 || y == 255 { 158 | p.pos[1] = js_sys::Math::random() * 255.0; 159 | } 160 | 161 | // Update the point's velocity based on looking up its position in the 162 | // vector field. 163 | p.vel[0] += v[0] / 362.0; 164 | p.vel[0] = clamp_vel(p.vel[0]); 165 | p.vel[1] += v[1] / 362.0; 166 | p.vel[1] = clamp_vel(p.vel[1]); 167 | 168 | // Update the point's position based on its current velocity. 169 | p.pos[0] += p.vel[0]; 170 | p.pos[0] %= WIDTH as f64; 171 | p.pos[1] += p.vel[1]; 172 | p.pos[1] %= WIDTH as f64; 173 | 174 | // Find the new index for this point within the frame buffer. 175 | let x = p.pos[0].round() as usize % WIDTH; 176 | let y = p.pos[1].round() as usize % HEIGHT; 177 | let idx = (x + y * WIDTH) * 4; 178 | 179 | // Make the color a little lighter where this point is in the frame 180 | // buffer. 181 | frame_buffer[idx + 0] = frame_buffer[idx + 0].saturating_add(p.color[0]); 182 | frame_buffer[idx + 1] = frame_buffer[idx + 1].saturating_add(p.color[1]); 183 | frame_buffer[idx + 2] = frame_buffer[idx + 2].saturating_add(p.color[2]); 184 | frame_buffer[idx + 3] = 255; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /entries/nebula-gen/src/utils.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | cfg_if! { 4 | // When the `console_error_panic_hook` feature is enabled, we can call the 5 | // `set_panic_hook` function at least once during initialization, and then 6 | // we will get better error messages if our code ever panics. 7 | // 8 | // For more details see 9 | // https://github.com/rustwasm/console_error_panic_hook#readme 10 | if #[cfg(feature = "console_error_panic_hook")] { 11 | extern crate console_error_panic_hook; 12 | pub use self::console_error_panic_hook::set_once as set_panic_hook; 13 | } else { 14 | #[inline] 15 | pub fn set_panic_hook() {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /howto.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | How to - One Page Wasm 6 | 7 | 8 |

One Page Wasm - How to

9 |
10 |

Write a Rust crate

11 |

It should expose a single function:

12 |

13 |

#[wasm_bindgen]
14 | pub fn frame(frame_buffer: &mut [u8], key_down: bool) {
15 |     // Your code here...
16 | }
17 |

18 |

Compile to WebAssembly with wasm-pack build

19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | One Page Wasm 6 | 43 | 44 | 45 |

One Page Wasm

46 |
47 |

Rules

48 |
    49 |
  1. Maximum one wasm page code size (65536 bytes)
  2. 50 |
  3. A frame buffer with one wasm page of pixels (256 x 256 = 65536 pixels)
  4. 51 |
  5. One button input (on or off)
  6. 52 |
53 |

See howto for more information. 54 |

55 |
56 |

Entries

57 |
    58 |
59 |
60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | async function getProjects() { 2 | const response = await fetch("./projects.json"); 3 | return response.json(); 4 | } 5 | 6 | const entries = document.getElementById("entries"); 7 | function renderProject(project) { 8 | const li = document.createElement("li"); 9 | li.innerHTML = ` 10 | 11 |

${project.name}

12 |

${project.size.total} bytes

13 | 14 |
15 |
16 | `; 17 | 18 | const iframe = li.querySelector("iframe"); 19 | iframe.addEventListener("load", () => iframe.contentWindow.postMessage({}, "*")); 20 | 21 | entries.appendChild(li); 22 | } 23 | 24 | async function main() { 25 | const projects = await getProjects(); 26 | for (const p of projects) { 27 | renderProject(p); 28 | } 29 | } 30 | 31 | main(); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-wasm-app", 3 | "version": "0.1.0", 4 | "description": "create an app to consume rust-generated wasm packages", 5 | "main": "index.js", 6 | "bin": { 7 | "create-wasm-app": ".bin/create-wasm-app.js" 8 | }, 9 | "scripts": { 10 | "build": "webpack --config webpack.config.js", 11 | "start": "webpack-dev-server" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/rustwasm/create-wasm-app.git" 16 | }, 17 | "keywords": [ 18 | "webassembly", 19 | "wasm", 20 | "rust", 21 | "webpack" 22 | ], 23 | "author": "Ashley Williams ", 24 | "license": "(MIT OR Apache-2.0)", 25 | "bugs": { 26 | "url": "https://github.com/rustwasm/create-wasm-app/issues" 27 | }, 28 | "homepage": "https://github.com/rustwasm/create-wasm-app#readme", 29 | "devDependencies": { 30 | "hello-wasm-pack": "^0.1.0", 31 | "webpack": "^4.16.3", 32 | "webpack-cli": "^3.1.0", 33 | "webpack-dev-server": "^3.1.5", 34 | "copy-webpack-plugin": "^4.5.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | beta 2 | -------------------------------------------------------------------------------- /template/bootstrap.js: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./XXX_MODULE.js") 5 | .then(mod => main(mod)) 6 | .catch(e => console.error("Error:", e)); 7 | 8 | const HEIGHT = 256; 9 | const WIDTH = 256 10 | 11 | let keyDown = false; 12 | window.addEventListener("keydown", () => keyDown = true); 13 | 14 | let shouldStop = false; 15 | window.addEventListener("message", () => shouldStop = true); 16 | 17 | async function main(mod) { 18 | const frameBuffer = new Uint8ClampedArray(HEIGHT * WIDTH * 4); 19 | 20 | while (true) { 21 | mod.frame(frameBuffer, keyDown); 22 | render(frameBuffer); 23 | 24 | // Always check this *after* rendering at least one frame, so that the index 25 | // page gets its preview images. 26 | if (shouldStop) { 27 | [...document.body.querySelectorAll("*")] 28 | .filter(e => e.tagName != "CANVAS") 29 | .forEach(e => e.setAttribute("hidden", "")); 30 | return; 31 | } 32 | 33 | keyDown = false; 34 | await new Promise(resolve => requestAnimationFrame(resolve)); 35 | } 36 | } 37 | 38 | const canvas = document.getElementById("canvas"); 39 | canvas.width = WIDTH; 40 | canvas.height = HEIGHT; 41 | 42 | const ctx = canvas.getContext("2d"); 43 | 44 | function render(frameBuffer) { 45 | let data = new ImageData(frameBuffer, WIDTH, HEIGHT); 46 | ctx.putImageData(data, 0, 0); 47 | } 48 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | XXX_MODULE 6 | 26 | 27 | 28 |
29 |

XXX_MODULE

30 |

Size of .wasm = XXX_WASM_SIZE bytes

31 |

Size of .js glue = XXX_JS_SIZE bytes

32 |

Total size = XXX_TOTAL_SIZE/65536 bytes

33 |

Source

34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /template/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./bootstrap.js", 6 | output: { 7 | path: path.resolve(__dirname, "dist"), 8 | filename: "bootstrap.js", 9 | }, 10 | mode: "development", 11 | plugins: [ 12 | new CopyWebpackPlugin(['index.html']) 13 | ], 14 | }; 15 | --------------------------------------------------------------------------------