├── .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 | - Maximum one wasm page code size (65536 bytes)
50 | - A frame buffer with one wasm page of pixels (256 x 256 = 65536 pixels)
51 | - One button input (on or off)
52 |
53 | See howto for more information.
54 |
55 |
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 |
--------------------------------------------------------------------------------