├── .gitignore ├── CNAME ├── README.md ├── package.json ├── index.html ├── rollup.config.js ├── utils.js ├── main.js ├── main-multi.js └── dist ├── bundle.js └── bundle-multi.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | assembly.generated.space 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apparatus Assembly 2 | 3 | Utilizing the Apparatus Generator in an animated example. 4 | 5 | Demo: https://kgolid.github.io/apparatus-assembly/ 6 | 7 | (Press 's' to toggle symmetric assembly on/off) 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apparatus-assembly", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/bundle.js", 6 | "mainMulti": "dist/bundle-multi.js", 7 | "keywords": [], 8 | "author": "Kjetil Golid", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/kgolid/apparatus-assemply.git" 13 | }, 14 | "dependencies": { 15 | "apparatus-generator": "^1.3.1", 16 | "chromotome": "^1.0.4" 17 | }, 18 | "devDependencies": { 19 | "rollup": "^0.64.1", 20 | "rollup-plugin-commonjs": "^9.2.0", 21 | "rollup-plugin-node-resolve": "^3.4.0" 22 | }, 23 | "scripts": { 24 | "build": "rollup -c", 25 | "dev": "rollup -c -w" 26 | }, 27 | "files": [ 28 | "dist" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | Apparatus Assembly 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import pkg from './package.json'; 4 | 5 | export default [ 6 | // browser-friendly UMD build 7 | { 8 | input: 'main.js', 9 | output: { 10 | name: 'apparatus-assembly', 11 | file: pkg.main, 12 | format: 'umd' 13 | }, 14 | plugins: [ 15 | resolve(), // so Rollup can find `ms` 16 | commonjs() // so Rollup can convert `ms` to an ES module 17 | ] 18 | }, 19 | { 20 | input: 'main-multi.js', 21 | output: { 22 | name: 'multi-apparatus-assembly', 23 | file: pkg.mainMulti, 24 | format: 'umd' 25 | }, 26 | plugins: [ 27 | resolve(), // so Rollup can find `ms` 28 | commonjs() // so Rollup can convert `ms` to an ES module 29 | ] 30 | } 31 | ]; 32 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | // List utils 2 | 3 | function contains(xs, y, eq) { 4 | return xs.some(x => eq(x, y)); 5 | } 6 | 7 | function union(xs, ys, eq) { 8 | let zs = []; 9 | xs.forEach(x => zs.push(x)); 10 | ys.forEach(y => { 11 | if (!contains(xs, y, eq)) zs.push(y); 12 | }); 13 | return zs; 14 | } 15 | 16 | function flatten(xs, eq) { 17 | return xs.reduce((acc, val) => union(acc, val, eq), []); 18 | } 19 | 20 | function get_random_from(list) { 21 | return list[Math.floor(Math.random() * list.length)] 22 | } 23 | 24 | // ---- Direction utils ---- 25 | 26 | // Get random direction among n choices. 27 | function random_dir(n) { 28 | if (n === 2) return Math.random() > 0.5 ? 0 : 2; // Up or down (50/50) 29 | if (n === 3) return Math.random() > 0.5 ? 1 : Math.random() > 0.5 ? 2 : 0; // Up, right or down (25/50/25) 30 | return Math.floor(Math.random() * n); // Up, right, down or left (25/25/25/25) 31 | } 32 | 33 | // Get horizontally mirrored direction. 34 | function mirror(dir) { 35 | if (is_vertical(dir)) return dir; 36 | if (dir == 1) return 3; 37 | if (dir == 3) return 1; 38 | } 39 | 40 | // Check wether direction is vertical. 41 | function is_vertical(dir) { 42 | return dir == 0 || dir == 2; 43 | } 44 | 45 | export { contains, union, flatten, get_random_from, random_dir, mirror, is_vertical }; 46 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import ApparatusGenerator from 'apparatus-generator'; 2 | import * as tome from 'chromotome'; 3 | import * as ut from './utils'; 4 | 5 | const sketch = p => { 6 | let app_gen, apparatus; 7 | let scale = 6; 8 | let shuffle = 220; 9 | let tick = 0; 10 | let final_frame_duration = 25; 11 | let symmetric_assembly = true; 12 | let movement_length = 0.82; 13 | 14 | p.setup = () => { 15 | p.createCanvas(800, 800); 16 | p.background('#eee8e2'); 17 | p.fill(0); 18 | p.frameRate(30); 19 | p.strokeWeight(3); 20 | p.stroke('#5c3936'); 21 | app_gen = new ApparatusGenerator(26, 36, { 22 | solidness: 0.5, 23 | initiate_chance: 0.9, 24 | extension_chance: 0.86, 25 | vertical_chance: 0.5, 26 | roundness: 0, 27 | group_size: 0.82, 28 | colors: tome.get('retro-washedout').colors 29 | }); 30 | 31 | setup_apparatus(); 32 | }; 33 | 34 | function setup_apparatus() { 35 | symmetric_assembly = true; 36 | apparatus = app_gen.generate(); 37 | populate_apparatus(apparatus); 38 | 39 | let chosen, origin, direction; 40 | let start_from_new_part = true; 41 | for (let i = final_frame_duration; i < shuffle; i++) { 42 | if (i === shuffle / 2) symmetric_assembly = false; 43 | 44 | apparatus.forEach(part => { 45 | part.path.push({ x: part.x1, y: part.y1 }); 46 | }); 47 | if (start_from_new_part) { 48 | chosen = ut.get_random_from(apparatus); 49 | origin = symmetric_assembly 50 | ? get_with_id(apparatus, chosen.id) 51 | : [chosen]; 52 | direction = 53 | symmetric_assembly && origin.length === 1 54 | ? ut.random_dir(2) 55 | : ut.random_dir(symmetric_assembly ? 3 : 4); 56 | } 57 | start_from_new_part = p.random() > movement_length; 58 | 59 | if (ut.is_vertical(direction) || !symmetric_assembly) { 60 | let neighborhood = get_neighborhood(origin, apparatus, direction); 61 | shift_all(neighborhood, direction, i); 62 | } else { 63 | let neighborhood_left = get_neighborhood( 64 | [origin[0]], 65 | apparatus, 66 | ut.mirror(direction) 67 | ); 68 | let neighborhood_right = get_neighborhood( 69 | [origin[1]], 70 | apparatus, 71 | direction 72 | ); 73 | shift_all(neighborhood_left, ut.mirror(direction), i); 74 | shift_all(neighborhood_right, direction, i); 75 | } 76 | } 77 | } 78 | 79 | function populate_apparatus(app) { 80 | app.forEach(part => { 81 | part.x2 = part.x1 + part.w; 82 | part.y2 = part.y1 + part.h; 83 | part.path = []; 84 | for (let i = 0; i < final_frame_duration; i++) { 85 | part.path.push({ x: part.x1, y: part.y1 }); 86 | } 87 | }); 88 | } 89 | 90 | p.draw = () => { 91 | p.background('#eee8e2'); 92 | p.translate( 93 | (p.width - (app_gen.xdim + 2) * scale) / 2, 94 | (p.height - (app_gen.ydim + 2) * scale) / 2 95 | ); 96 | 97 | if (tick >= shuffle) { 98 | setup_apparatus(); 99 | tick = 0; 100 | } 101 | apparatus.forEach(part => { 102 | display_rect(part, scale, shuffle - tick - 1); 103 | }); 104 | tick++; 105 | }; 106 | 107 | function get_neighborhood(ps, rs, dir) { 108 | let ns = ps; 109 | let ms = ut.union( 110 | ns, 111 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 112 | equal_rect 113 | ); 114 | 115 | while (ms.length > ns.length) { 116 | ns = ms; 117 | ms = ut.union( 118 | ns, 119 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 120 | equal_rect 121 | ); 122 | } 123 | return ms; 124 | } 125 | 126 | function get_neighbors(r1, rs, dir) { 127 | return rs.filter(r => is_neighbor(r1, r, dir)); 128 | } 129 | 130 | function is_neighbor(r1, r2, dir) { 131 | if (equal_rect(r1, r2)) return false; // Identical 132 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North 133 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East 134 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South 135 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West 136 | return false; // Error 137 | } 138 | 139 | function equal_rect(r1, r2) { 140 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2; 141 | } 142 | 143 | function shift_all(rs, dir, time) { 144 | rs.forEach(r => shift(r, dir, time)); 145 | } 146 | 147 | function shift(r, dir, time) { 148 | let sx = ut.is_vertical(dir) ? 0 : dir == 1 ? 1 : -1; 149 | let sy = !ut.is_vertical(dir) ? 0 : dir == 2 ? 1 : -1; 150 | 151 | r.x1 += sx; 152 | r.y1 += sy; 153 | r.x2 += sx; 154 | r.y2 += sy; 155 | r.path[time] = { x: r.x1, y: r.y1 }; 156 | } 157 | 158 | function get_with_id(rs, id) { 159 | return rs.filter(r => r.id === id); 160 | } 161 | 162 | function display_rect(r, scale, time) { 163 | p.fill(r.col ? r.col : '#fff'); 164 | p.rect( 165 | r.path[time].x * scale, 166 | r.path[time].y * scale, 167 | r.w * scale, 168 | r.h * scale 169 | ); 170 | } 171 | 172 | p.keyPressed = () => { 173 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly; 174 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png'); 175 | }; 176 | }; 177 | 178 | new p5(sketch); 179 | -------------------------------------------------------------------------------- /main-multi.js: -------------------------------------------------------------------------------- 1 | import ApparatusGenerator from 'apparatus-generator'; 2 | import * as tome from 'chromotome'; 3 | import * as ut from './utils'; 4 | 5 | const sketch = p => { 6 | let app_gen, apparata; 7 | const number_of_apparata = 9; 8 | const scale = 2.5; 9 | const shuffle = 300; 10 | let tick = 0; 11 | const final_frame_duration = 120; 12 | let symmetric_assembly = true; 13 | const movement_length = 0.85; 14 | const row_length = 3; 15 | 16 | p.setup = () => { 17 | p.createCanvas(1200, 1000); 18 | p.background('#eee8e2'); 19 | p.fill(0); 20 | p.frameRate(40); 21 | p.strokeWeight(1.5); 22 | p.stroke('#5c3936'); 23 | 24 | apparata = []; 25 | app_gen = new ApparatusGenerator(25, 35, { 26 | solidness: 0.5, 27 | initiate_chance: 0.9, 28 | extension_chance: 0.88, 29 | vertical_chance: 0.6, 30 | roundness: 0, 31 | group_size: 0.82, 32 | colors: tome.get('retro-washedout').colors 33 | }); 34 | 35 | for (let i = 0; i < number_of_apparata / row_length; i++) { 36 | setup_apparatus(row_length); 37 | } 38 | }; 39 | 40 | function setup_apparatus(n) { 41 | let apparatus = app_gen.generate(); 42 | 43 | for (let i = 0; i < n; i++) { 44 | let extra_shuffle = p.floor(p.random(100)); 45 | let animatable_app = populate_apparatus(apparatus, extra_shuffle); 46 | animate_apparatus(animatable_app, extra_shuffle); 47 | apparata.push(animatable_app); 48 | } 49 | } 50 | 51 | function populate_apparatus(app, extra_shuffle) { 52 | let new_app = []; 53 | app.forEach(part => { 54 | let new_part = { 55 | ...part, 56 | x2: part.x1 + part.w, 57 | y2: part.y1 + part.h, 58 | path: [] 59 | }; 60 | for (let i = 0; i < final_frame_duration - extra_shuffle; i++) { 61 | new_part.path.push({ x: new_part.x1, y: new_part.y1 }); 62 | } 63 | new_app.push(new_part); 64 | }); 65 | return new_app; 66 | } 67 | 68 | function animate_apparatus(apparatus, extra_shuffle) { 69 | symmetric_assembly = true; 70 | let chosen, origin, direction; 71 | let start_from_new_part = true; 72 | let actual_ff_duration = final_frame_duration - extra_shuffle; 73 | for (let i = actual_ff_duration; i < shuffle; i++) { 74 | if (i - actual_ff_duration >= (shuffle - actual_ff_duration) / 2) { 75 | symmetric_assembly = false; 76 | } 77 | 78 | apparatus.forEach(part => { 79 | part.path.push({ x: part.x1, y: part.y1 }); 80 | }); 81 | if (start_from_new_part) { 82 | chosen = ut.get_random_from(apparatus); 83 | origin = symmetric_assembly 84 | ? get_with_id(apparatus, chosen.id) 85 | : [chosen]; 86 | direction = 87 | symmetric_assembly && origin.length === 1 88 | ? ut.random_dir(2) 89 | : ut.random_dir(symmetric_assembly ? 3 : 4); 90 | } 91 | start_from_new_part = p.random() > movement_length; 92 | 93 | if (ut.is_vertical(direction) || !symmetric_assembly) { 94 | let neighborhood = get_neighborhood(origin, apparatus, direction); 95 | shift_all(neighborhood, direction, i); 96 | } else { 97 | let neighborhood_left = get_neighborhood( 98 | [origin[0]], 99 | apparatus, 100 | ut.mirror(direction) 101 | ); 102 | let neighborhood_right = get_neighborhood( 103 | [origin[1]], 104 | apparatus, 105 | direction 106 | ); 107 | shift_all(neighborhood_left, ut.mirror(direction), i); 108 | shift_all(neighborhood_right, direction, i); 109 | } 110 | } 111 | } 112 | 113 | p.draw = () => { 114 | p.background('#eee8e2'); 115 | p.translate( 116 | (p.width - (app_gen.xdim + 2) * scale) / (row_length + 1), 117 | (p.height - (app_gen.ydim + 2) * scale) / 6 118 | ); 119 | 120 | if (tick >= shuffle) { 121 | apparata = []; 122 | for (let i = 0; i < number_of_apparata / row_length; i++) { 123 | setup_apparatus(row_length); 124 | } 125 | tick = 0; 126 | } 127 | 128 | for (let i = 0; i < apparata.length; i++) { 129 | display_apparatus(apparata[i]); 130 | p.translate((p.width - (app_gen.xdim + 2) * scale) / (row_length + 1), 0); 131 | if (i % row_length === row_length - 1) 132 | p.translate( 133 | (-row_length * (p.width - (app_gen.xdim + 2) * scale)) / 134 | (row_length + 1), 135 | (p.height - (app_gen.ydim + 2) * scale) / 3 136 | ); 137 | } 138 | 139 | tick++; 140 | }; 141 | 142 | function display_apparatus(appar) { 143 | appar.forEach(part => { 144 | display_rect(part, scale, shuffle - tick - 1); 145 | }); 146 | } 147 | 148 | function get_neighborhood(ps, rs, dir) { 149 | let ns = ps; 150 | let ms = ut.union( 151 | ns, 152 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 153 | equal_rect 154 | ); 155 | 156 | while (ms.length > ns.length) { 157 | ns = ms; 158 | ms = ut.union( 159 | ns, 160 | ut.flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 161 | equal_rect 162 | ); 163 | } 164 | return ms; 165 | } 166 | 167 | function get_neighbors(r1, rs, dir) { 168 | return rs.filter(r => is_neighbor(r1, r, dir)); 169 | } 170 | 171 | function is_neighbor(r1, r2, dir) { 172 | if (equal_rect(r1, r2)) return false; // Identical 173 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North 174 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East 175 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South 176 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West 177 | return false; // Error 178 | } 179 | 180 | function equal_rect(r1, r2) { 181 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2; 182 | } 183 | 184 | function shift_all(rs, dir, time) { 185 | rs.forEach(r => shift(r, dir, time)); 186 | } 187 | 188 | function shift(r, dir, time) { 189 | let sx = ut.is_vertical(dir) ? 0 : dir == 1 ? 1 : -1; 190 | let sy = !ut.is_vertical(dir) ? 0 : dir == 2 ? 1 : -1; 191 | 192 | r.x1 += sx; 193 | r.y1 += sy; 194 | r.x2 += sx; 195 | r.y2 += sy; 196 | r.path[time] = { x: r.x1, y: r.y1 }; 197 | } 198 | 199 | function get_with_id(rs, id) { 200 | return rs.filter(r => r.id === id); 201 | } 202 | 203 | function display_rect(r, scale, time) { 204 | p.fill(r.col ? r.col : '#fff'); 205 | p.rect( 206 | r.path[time].x * scale, 207 | r.path[time].y * scale, 208 | r.w * scale, 209 | r.h * scale 210 | ); 211 | } 212 | 213 | p.keyPressed = () => { 214 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly; 215 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png'); 216 | }; 217 | }; 218 | 219 | new p5(sketch); 220 | -------------------------------------------------------------------------------- /dist/bundle.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | class index { 8 | constructor( 9 | width, 10 | height, 11 | { 12 | initiate_chance = 0.8, 13 | extension_chance = 0.8, 14 | vertical_chance = 0.8, 15 | horizontal_symmetry = true, 16 | vertical_symmetry = false, 17 | roundness = 0.1, 18 | solidness = 0.5, 19 | colors = [], 20 | color_mode = 'group', 21 | group_size = 0.8, 22 | simple = false, 23 | } = {} 24 | ) { 25 | this.xdim = Math.round(width * 2 + 11, 0); 26 | this.ydim = Math.round(height * 2 + 11, 0); 27 | this.radius_x = width; 28 | this.radius_y = height; 29 | this.chance_new = initiate_chance; 30 | this.chance_extend = extension_chance; 31 | this.chance_vertical = vertical_chance; 32 | this.colors = colors; 33 | this.color_mode = color_mode; 34 | this.group_size = group_size; 35 | this.h_symmetric = horizontal_symmetry; 36 | this.v_symmetric = vertical_symmetry; 37 | this.roundness = roundness; 38 | this.solidness = solidness; 39 | this.simple = simple; 40 | } 41 | 42 | generate() { 43 | this.main_color = get_random(this.colors); 44 | this.id_counter = 0; 45 | 46 | let grid = new Array(this.ydim + 1); 47 | for (var i = 0; i < grid.length; i++) { 48 | grid[i] = new Array(this.xdim + 1); 49 | for (var j = 0; j < grid[i].length; j++) { 50 | if (i == 0 || j == 0) { 51 | grid[i][j] = { h: false, v: false, in: false, col: null }; 52 | } else if (this.h_symmetric && j > grid[i].length / 2) { 53 | grid[i][j] = deep_copy(grid[i][grid[i].length - j]); 54 | grid[i][j].v = grid[i][grid[i].length - j + 1].v; 55 | } else if (this.v_symmetric && i > grid.length / 2) { 56 | grid[i][j] = deep_copy(grid[grid.length - i][j]); 57 | grid[i][j].h = grid[grid.length - i + 1][j].h; 58 | } else { 59 | grid[i][j] = this.next_block(j, i, grid[i][j - 1], grid[i - 1][j]); 60 | } 61 | } 62 | } 63 | let rects = convert_linegrid_to_rectangles(grid); 64 | return rects; 65 | } 66 | 67 | next_block(x, y, left, top) { 68 | const context = this; 69 | 70 | if (!left.in && !top.in) { 71 | return block_set_1(x, y); 72 | } 73 | 74 | if (left.in && !top.in) { 75 | if (left.h) return block_set_3(x, y); 76 | return block_set_2(x, y); 77 | } 78 | 79 | if (!left.in && top.in) { 80 | if (top.v) return block_set_5(x, y); 81 | return block_set_4(x, y); 82 | } 83 | 84 | if (left.in && top.in) { 85 | if (!left.h && !top.v) return block_set_6(); 86 | if (left.h && !top.v) return block_set_7(x, y); 87 | if (!left.h && top.v) return block_set_8(x, y); 88 | return block_set_9(x, y); 89 | } 90 | 91 | // --- Block sets ---- 92 | 93 | function block_set_1(x, y) { 94 | if (start_new_from_blank(x, y)) return new_block(); 95 | return { v: false, h: false, in: false, col: null, id: null }; 96 | } 97 | 98 | function block_set_2(x, y) { 99 | if (start_new_from_blank(x, y)) return new_block(); 100 | return { v: true, h: false, in: false, col: null, id: null }; 101 | } 102 | 103 | function block_set_3(x, y) { 104 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 105 | return block_set_2(x, y); 106 | } 107 | 108 | function block_set_4(x, y) { 109 | if (start_new_from_blank(x, y)) return new_block(); 110 | return { v: false, h: true, in: false, col: null, id: null }; 111 | } 112 | 113 | function block_set_5(x, y) { 114 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 115 | return block_set_4(x, y); 116 | } 117 | 118 | function block_set_6() { 119 | return { v: false, h: false, in: true, col: left.col, id: left.id }; 120 | } 121 | 122 | function block_set_7(x, y) { 123 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 124 | if (start_new(x, y)) return new_block(); 125 | return { v: true, h: true, in: false, col: null, id: null }; 126 | } 127 | 128 | function block_set_8(x, y) { 129 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 130 | if (start_new(x, y)) return new_block(); 131 | return { v: true, h: true, in: false, col: null, id: null }; 132 | } 133 | 134 | function block_set_9(x, y) { 135 | if (vertical_dir()) return { v: true, h: false, in: true, col: top.col, id: top.id }; 136 | return { v: false, h: true, in: true, col: left.col, id: left.id }; 137 | } 138 | 139 | // ---- Blocks ---- 140 | 141 | function new_block() { 142 | let col; 143 | if (context.color_mode === 'random') { 144 | col = get_random(context.colors); 145 | } else if (context.color_mode === 'main') { 146 | col = Math.random() > 0.75 ? get_random(context.colors) : context.main_color; 147 | } else if (context.color_mode === 'group') { 148 | let keep = Math.random() > 0.5 ? left.col : top.col; 149 | context.main_color = 150 | Math.random() > context.group_size ? get_random(context.colors) : keep || context.main_color; 151 | col = context.main_color; 152 | } else { 153 | col = context.main_color; 154 | } 155 | 156 | return { v: true, h: true, in: true, col: col, id: context.id_counter++ }; 157 | } 158 | 159 | // ---- Decisions ---- 160 | 161 | function start_new_from_blank(x, y) { 162 | if (context.simple) return true; 163 | if (!active_position(x, y, -1 * (1 - context.roundness))) return false; 164 | return Math.random() <= context.solidness; 165 | } 166 | 167 | function start_new(x, y) { 168 | if (context.simple) return true; 169 | if (!active_position(x, y, 0)) return false; 170 | return Math.random() <= context.chance_new; 171 | } 172 | 173 | function extend(x, y) { 174 | if (!active_position(x, y, 1 - context.roundness) && !context.simple) return false; 175 | return Math.random() <= context.chance_extend; 176 | } 177 | 178 | function vertical_dir() { 179 | return Math.random() <= context.chance_vertical; 180 | } 181 | 182 | function active_position(x, y, fuzzy) { 183 | let fuzziness = 1 + Math.random() * fuzzy; 184 | let xa = Math.pow(x - context.xdim / 2, 2) / Math.pow(context.radius_x * fuzziness, 2); 185 | let ya = Math.pow(y - context.ydim / 2, 2) / Math.pow(context.radius_y * fuzziness, 2); 186 | return xa + ya < 1; 187 | } 188 | } 189 | } 190 | 191 | function get_random(array) { 192 | return array[Math.floor(Math.random() * array.length)]; 193 | } 194 | 195 | function deep_copy(obj) { 196 | let nobj = []; 197 | for (var key in obj) { 198 | if (obj.hasOwnProperty(key)) { 199 | nobj[key] = obj[key]; 200 | } 201 | } 202 | return nobj; 203 | } 204 | 205 | // --- Conversion --- 206 | function convert_linegrid_to_rectangles(grid) { 207 | let nw_corners = get_nw_corners(grid); 208 | extend_corners_to_rectangles(nw_corners, grid); 209 | return nw_corners; 210 | } 211 | 212 | function get_nw_corners(grid) { 213 | let nw_corners = []; 214 | for (let i = 0; i < grid.length; i++) { 215 | for (let j = 0; j < grid[i].length; j++) { 216 | let cell = grid[i][j]; 217 | if (cell.h && cell.v && cell.in) nw_corners.push({ x1: j, y1: i, col: cell.col, id: cell.id }); 218 | } 219 | } 220 | return nw_corners; 221 | } 222 | 223 | function extend_corners_to_rectangles(corners, grid) { 224 | corners.map(c => { 225 | let accx = 1; 226 | while (c.x1 + accx < grid[c.y1].length && !grid[c.y1][c.x1 + accx].v) { 227 | accx++; 228 | } 229 | let accy = 1; 230 | while (c.y1 + accy < grid.length && !grid[c.y1 + accy][c.x1].h) { 231 | accy++; 232 | } 233 | c.w = accx; 234 | c.h = accy; 235 | return c; 236 | }); 237 | } 238 | 239 | const palettes = [ 240 | { 241 | name: 'tundra1', 242 | colors: ['#40708c', '#8e998c', '#5d3f37', '#ed6954', '#f2e9e2'] 243 | }, 244 | { 245 | name: 'tundra2', 246 | colors: ['#5f9e93', '#3d3638', '#733632', '#b66239', '#b0a1a4', '#e3dad2'] 247 | }, 248 | { 249 | name: 'tundra3', 250 | colors: [ 251 | '#87c3ca', 252 | '#7b7377', 253 | '#b2475d', 254 | '#7d3e3e', 255 | '#eb7f64', 256 | '#d9c67a', 257 | '#f3f2f2' 258 | ] 259 | }, 260 | { 261 | name: 'tundra4', 262 | colors: [ 263 | '#d53939', 264 | '#b6754d', 265 | '#a88d5f', 266 | '#524643', 267 | '#3c5a53', 268 | '#7d8c7c', 269 | '#dad6cd' 270 | ] 271 | }, 272 | { 273 | name: 'retro', 274 | colors: [ 275 | '#69766f', 276 | '#9ed6cb', 277 | '#f7e5cc', 278 | '#9d8f7f', 279 | '#936454', 280 | '#bf5c32', 281 | '#efad57' 282 | ] 283 | }, 284 | { 285 | name: 'retro-washedout', 286 | colors: [ 287 | '#878a87', 288 | '#cbdbc8', 289 | '#e8e0d4', 290 | '#b29e91', 291 | '#9f736c', 292 | '#b76254', 293 | '#dfa372' 294 | ] 295 | }, 296 | { 297 | name: 'roygbiv-warm', 298 | colors: [ 299 | '#705f84', 300 | '#687d99', 301 | '#6c843e', 302 | '#fc9a1a', 303 | '#dc383a', 304 | '#aa3a33', 305 | '#9c4257' 306 | ] 307 | }, 308 | { 309 | name: 'roygbiv-toned', 310 | colors: [ 311 | '#817c77', 312 | '#396c68', 313 | '#89e3b7', 314 | '#f59647', 315 | '#d63644', 316 | '#893f49', 317 | '#4d3240' 318 | ] 319 | }, 320 | { 321 | name: 'present-correct', 322 | colors: [ 323 | '#fd3741', 324 | '#fe4f11', 325 | '#ff6800', 326 | '#ffa61a', 327 | '#ffc219', 328 | '#ffd114', 329 | '#fcd82e', 330 | '#f4d730', 331 | '#ced562', 332 | '#8ac38f', 333 | '#79b7a0', 334 | '#72b5b1', 335 | '#5b9bae', 336 | '#6ba1b7', 337 | '#49619d', 338 | '#604791', 339 | '#721e7f', 340 | '#9b2b77', 341 | '#ab2562', 342 | '#ca2847' 343 | ] 344 | } 345 | ]; 346 | 347 | var palettes$1 = palettes.map(p => { 348 | p.size = p.colors.length; 349 | return p; 350 | }); 351 | 352 | function get(name) { 353 | return palettes$1.find(pal => pal.name == name); 354 | } 355 | 356 | // List utils 357 | 358 | function contains(xs, y, eq) { 359 | return xs.some(x => eq(x, y)); 360 | } 361 | 362 | function union(xs, ys, eq) { 363 | let zs = []; 364 | xs.forEach(x => zs.push(x)); 365 | ys.forEach(y => { 366 | if (!contains(xs, y, eq)) zs.push(y); 367 | }); 368 | return zs; 369 | } 370 | 371 | function flatten(xs, eq) { 372 | return xs.reduce((acc, val) => union(acc, val, eq), []); 373 | } 374 | 375 | function get_random_from(list) { 376 | return list[Math.floor(Math.random() * list.length)] 377 | } 378 | 379 | // ---- Direction utils ---- 380 | 381 | // Get random direction among n choices. 382 | function random_dir(n) { 383 | if (n === 2) return Math.random() > 0.5 ? 0 : 2; // Up or down (50/50) 384 | if (n === 3) return Math.random() > 0.5 ? 1 : Math.random() > 0.5 ? 2 : 0; // Up, right or down (25/50/25) 385 | return Math.floor(Math.random() * n); // Up, right, down or left (25/25/25/25) 386 | } 387 | 388 | // Get horizontally mirrored direction. 389 | function mirror(dir) { 390 | if (is_vertical(dir)) return dir; 391 | if (dir == 1) return 3; 392 | if (dir == 3) return 1; 393 | } 394 | 395 | // Check wether direction is vertical. 396 | function is_vertical(dir) { 397 | return dir == 0 || dir == 2; 398 | } 399 | 400 | const sketch = p => { 401 | let app_gen, apparatus; 402 | let scale = 6; 403 | let shuffle = 220; 404 | let tick = 0; 405 | let final_frame_duration = 25; 406 | let symmetric_assembly = true; 407 | let movement_length = 0.82; 408 | 409 | p.setup = () => { 410 | p.createCanvas(800, 800); 411 | p.background('#eee8e2'); 412 | p.fill(0); 413 | p.frameRate(30); 414 | p.strokeWeight(3); 415 | p.stroke('#5c3936'); 416 | app_gen = new index(26, 36, { 417 | solidness: 0.5, 418 | initiate_chance: 0.9, 419 | extension_chance: 0.86, 420 | vertical_chance: 0.5, 421 | roundness: 0, 422 | group_size: 0.82, 423 | colors: get('retro-washedout').colors 424 | }); 425 | 426 | setup_apparatus(); 427 | }; 428 | 429 | function setup_apparatus() { 430 | symmetric_assembly = true; 431 | apparatus = app_gen.generate(); 432 | populate_apparatus(apparatus); 433 | 434 | let chosen, origin, direction; 435 | let start_from_new_part = true; 436 | for (let i = final_frame_duration; i < shuffle; i++) { 437 | if (i === shuffle / 2) symmetric_assembly = false; 438 | 439 | apparatus.forEach(part => { 440 | part.path.push({ x: part.x1, y: part.y1 }); 441 | }); 442 | if (start_from_new_part) { 443 | chosen = get_random_from(apparatus); 444 | origin = symmetric_assembly 445 | ? get_with_id(apparatus, chosen.id) 446 | : [chosen]; 447 | direction = 448 | symmetric_assembly && origin.length === 1 449 | ? random_dir(2) 450 | : random_dir(symmetric_assembly ? 3 : 4); 451 | } 452 | start_from_new_part = p.random() > movement_length; 453 | 454 | if (is_vertical(direction) || !symmetric_assembly) { 455 | let neighborhood = get_neighborhood(origin, apparatus, direction); 456 | shift_all(neighborhood, direction, i); 457 | } else { 458 | let neighborhood_left = get_neighborhood( 459 | [origin[0]], 460 | apparatus, 461 | mirror(direction) 462 | ); 463 | let neighborhood_right = get_neighborhood( 464 | [origin[1]], 465 | apparatus, 466 | direction 467 | ); 468 | shift_all(neighborhood_left, mirror(direction), i); 469 | shift_all(neighborhood_right, direction, i); 470 | } 471 | } 472 | } 473 | 474 | function populate_apparatus(app) { 475 | app.forEach(part => { 476 | part.x2 = part.x1 + part.w; 477 | part.y2 = part.y1 + part.h; 478 | part.path = []; 479 | for (let i = 0; i < final_frame_duration; i++) { 480 | part.path.push({ x: part.x1, y: part.y1 }); 481 | } 482 | }); 483 | } 484 | 485 | p.draw = () => { 486 | p.background('#eee8e2'); 487 | p.translate( 488 | (p.width - (app_gen.xdim + 2) * scale) / 2, 489 | (p.height - (app_gen.ydim + 2) * scale) / 2 490 | ); 491 | 492 | if (tick >= shuffle) { 493 | setup_apparatus(); 494 | tick = 0; 495 | } 496 | apparatus.forEach(part => { 497 | display_rect(part, scale, shuffle - tick - 1); 498 | }); 499 | tick++; 500 | }; 501 | 502 | function get_neighborhood(ps, rs, dir) { 503 | let ns = ps; 504 | let ms = union( 505 | ns, 506 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 507 | equal_rect 508 | ); 509 | 510 | while (ms.length > ns.length) { 511 | ns = ms; 512 | ms = union( 513 | ns, 514 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 515 | equal_rect 516 | ); 517 | } 518 | return ms; 519 | } 520 | 521 | function get_neighbors(r1, rs, dir) { 522 | return rs.filter(r => is_neighbor(r1, r, dir)); 523 | } 524 | 525 | function is_neighbor(r1, r2, dir) { 526 | if (equal_rect(r1, r2)) return false; // Identical 527 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North 528 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East 529 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South 530 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West 531 | return false; // Error 532 | } 533 | 534 | function equal_rect(r1, r2) { 535 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2; 536 | } 537 | 538 | function shift_all(rs, dir, time) { 539 | rs.forEach(r => shift(r, dir, time)); 540 | } 541 | 542 | function shift(r, dir, time) { 543 | let sx = is_vertical(dir) ? 0 : dir == 1 ? 1 : -1; 544 | let sy = !is_vertical(dir) ? 0 : dir == 2 ? 1 : -1; 545 | 546 | r.x1 += sx; 547 | r.y1 += sy; 548 | r.x2 += sx; 549 | r.y2 += sy; 550 | r.path[time] = { x: r.x1, y: r.y1 }; 551 | } 552 | 553 | function get_with_id(rs, id) { 554 | return rs.filter(r => r.id === id); 555 | } 556 | 557 | function display_rect(r, scale, time) { 558 | p.fill(r.col ? r.col : '#fff'); 559 | p.rect( 560 | r.path[time].x * scale, 561 | r.path[time].y * scale, 562 | r.w * scale, 563 | r.h * scale 564 | ); 565 | } 566 | 567 | p.keyPressed = () => { 568 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly; 569 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png'); 570 | }; 571 | }; 572 | 573 | new p5(sketch); 574 | 575 | }))); 576 | -------------------------------------------------------------------------------- /dist/bundle-multi.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | class index { 8 | constructor( 9 | width, 10 | height, 11 | { 12 | initiate_chance = 0.8, 13 | extension_chance = 0.8, 14 | vertical_chance = 0.8, 15 | horizontal_symmetry = true, 16 | vertical_symmetry = false, 17 | roundness = 0.1, 18 | solidness = 0.5, 19 | colors = [], 20 | color_mode = 'group', 21 | group_size = 0.8, 22 | simple = false, 23 | } = {} 24 | ) { 25 | this.xdim = Math.round(width * 2 + 11, 0); 26 | this.ydim = Math.round(height * 2 + 11, 0); 27 | this.radius_x = width; 28 | this.radius_y = height; 29 | this.chance_new = initiate_chance; 30 | this.chance_extend = extension_chance; 31 | this.chance_vertical = vertical_chance; 32 | this.colors = colors; 33 | this.color_mode = color_mode; 34 | this.group_size = group_size; 35 | this.h_symmetric = horizontal_symmetry; 36 | this.v_symmetric = vertical_symmetry; 37 | this.roundness = roundness; 38 | this.solidness = solidness; 39 | this.simple = simple; 40 | } 41 | 42 | generate() { 43 | this.main_color = get_random(this.colors); 44 | this.id_counter = 0; 45 | 46 | let grid = new Array(this.ydim + 1); 47 | for (var i = 0; i < grid.length; i++) { 48 | grid[i] = new Array(this.xdim + 1); 49 | for (var j = 0; j < grid[i].length; j++) { 50 | if (i == 0 || j == 0) { 51 | grid[i][j] = { h: false, v: false, in: false, col: null }; 52 | } else if (this.h_symmetric && j > grid[i].length / 2) { 53 | grid[i][j] = deep_copy(grid[i][grid[i].length - j]); 54 | grid[i][j].v = grid[i][grid[i].length - j + 1].v; 55 | } else if (this.v_symmetric && i > grid.length / 2) { 56 | grid[i][j] = deep_copy(grid[grid.length - i][j]); 57 | grid[i][j].h = grid[grid.length - i + 1][j].h; 58 | } else { 59 | grid[i][j] = this.next_block(j, i, grid[i][j - 1], grid[i - 1][j]); 60 | } 61 | } 62 | } 63 | let rects = convert_linegrid_to_rectangles(grid); 64 | return rects; 65 | } 66 | 67 | next_block(x, y, left, top) { 68 | const context = this; 69 | 70 | if (!left.in && !top.in) { 71 | return block_set_1(x, y); 72 | } 73 | 74 | if (left.in && !top.in) { 75 | if (left.h) return block_set_3(x, y); 76 | return block_set_2(x, y); 77 | } 78 | 79 | if (!left.in && top.in) { 80 | if (top.v) return block_set_5(x, y); 81 | return block_set_4(x, y); 82 | } 83 | 84 | if (left.in && top.in) { 85 | if (!left.h && !top.v) return block_set_6(); 86 | if (left.h && !top.v) return block_set_7(x, y); 87 | if (!left.h && top.v) return block_set_8(x, y); 88 | return block_set_9(x, y); 89 | } 90 | 91 | // --- Block sets ---- 92 | 93 | function block_set_1(x, y) { 94 | if (start_new_from_blank(x, y)) return new_block(); 95 | return { v: false, h: false, in: false, col: null, id: null }; 96 | } 97 | 98 | function block_set_2(x, y) { 99 | if (start_new_from_blank(x, y)) return new_block(); 100 | return { v: true, h: false, in: false, col: null, id: null }; 101 | } 102 | 103 | function block_set_3(x, y) { 104 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 105 | return block_set_2(x, y); 106 | } 107 | 108 | function block_set_4(x, y) { 109 | if (start_new_from_blank(x, y)) return new_block(); 110 | return { v: false, h: true, in: false, col: null, id: null }; 111 | } 112 | 113 | function block_set_5(x, y) { 114 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 115 | return block_set_4(x, y); 116 | } 117 | 118 | function block_set_6() { 119 | return { v: false, h: false, in: true, col: left.col, id: left.id }; 120 | } 121 | 122 | function block_set_7(x, y) { 123 | if (extend(x, y)) return { v: false, h: true, in: true, col: left.col, id: left.id }; 124 | if (start_new(x, y)) return new_block(); 125 | return { v: true, h: true, in: false, col: null, id: null }; 126 | } 127 | 128 | function block_set_8(x, y) { 129 | if (extend(x, y)) return { v: true, h: false, in: true, col: top.col, id: top.id }; 130 | if (start_new(x, y)) return new_block(); 131 | return { v: true, h: true, in: false, col: null, id: null }; 132 | } 133 | 134 | function block_set_9(x, y) { 135 | if (vertical_dir()) return { v: true, h: false, in: true, col: top.col, id: top.id }; 136 | return { v: false, h: true, in: true, col: left.col, id: left.id }; 137 | } 138 | 139 | // ---- Blocks ---- 140 | 141 | function new_block() { 142 | let col; 143 | if (context.color_mode === 'random') { 144 | col = get_random(context.colors); 145 | } else if (context.color_mode === 'main') { 146 | col = Math.random() > 0.75 ? get_random(context.colors) : context.main_color; 147 | } else if (context.color_mode === 'group') { 148 | let keep = Math.random() > 0.5 ? left.col : top.col; 149 | context.main_color = 150 | Math.random() > context.group_size ? get_random(context.colors) : keep || context.main_color; 151 | col = context.main_color; 152 | } else { 153 | col = context.main_color; 154 | } 155 | 156 | return { v: true, h: true, in: true, col: col, id: context.id_counter++ }; 157 | } 158 | 159 | // ---- Decisions ---- 160 | 161 | function start_new_from_blank(x, y) { 162 | if (context.simple) return true; 163 | if (!active_position(x, y, -1 * (1 - context.roundness))) return false; 164 | return Math.random() <= context.solidness; 165 | } 166 | 167 | function start_new(x, y) { 168 | if (context.simple) return true; 169 | if (!active_position(x, y, 0)) return false; 170 | return Math.random() <= context.chance_new; 171 | } 172 | 173 | function extend(x, y) { 174 | if (!active_position(x, y, 1 - context.roundness) && !context.simple) return false; 175 | return Math.random() <= context.chance_extend; 176 | } 177 | 178 | function vertical_dir() { 179 | return Math.random() <= context.chance_vertical; 180 | } 181 | 182 | function active_position(x, y, fuzzy) { 183 | let fuzziness = 1 + Math.random() * fuzzy; 184 | let xa = Math.pow(x - context.xdim / 2, 2) / Math.pow(context.radius_x * fuzziness, 2); 185 | let ya = Math.pow(y - context.ydim / 2, 2) / Math.pow(context.radius_y * fuzziness, 2); 186 | return xa + ya < 1; 187 | } 188 | } 189 | } 190 | 191 | function get_random(array) { 192 | return array[Math.floor(Math.random() * array.length)]; 193 | } 194 | 195 | function deep_copy(obj) { 196 | let nobj = []; 197 | for (var key in obj) { 198 | if (obj.hasOwnProperty(key)) { 199 | nobj[key] = obj[key]; 200 | } 201 | } 202 | return nobj; 203 | } 204 | 205 | // --- Conversion --- 206 | function convert_linegrid_to_rectangles(grid) { 207 | let nw_corners = get_nw_corners(grid); 208 | extend_corners_to_rectangles(nw_corners, grid); 209 | return nw_corners; 210 | } 211 | 212 | function get_nw_corners(grid) { 213 | let nw_corners = []; 214 | for (let i = 0; i < grid.length; i++) { 215 | for (let j = 0; j < grid[i].length; j++) { 216 | let cell = grid[i][j]; 217 | if (cell.h && cell.v && cell.in) nw_corners.push({ x1: j, y1: i, col: cell.col, id: cell.id }); 218 | } 219 | } 220 | return nw_corners; 221 | } 222 | 223 | function extend_corners_to_rectangles(corners, grid) { 224 | corners.map(c => { 225 | let accx = 1; 226 | while (c.x1 + accx < grid[c.y1].length && !grid[c.y1][c.x1 + accx].v) { 227 | accx++; 228 | } 229 | let accy = 1; 230 | while (c.y1 + accy < grid.length && !grid[c.y1 + accy][c.x1].h) { 231 | accy++; 232 | } 233 | c.w = accx; 234 | c.h = accy; 235 | return c; 236 | }); 237 | } 238 | 239 | const palettes = [ 240 | { 241 | name: 'tundra1', 242 | colors: ['#40708c', '#8e998c', '#5d3f37', '#ed6954', '#f2e9e2'] 243 | }, 244 | { 245 | name: 'tundra2', 246 | colors: ['#5f9e93', '#3d3638', '#733632', '#b66239', '#b0a1a4', '#e3dad2'] 247 | }, 248 | { 249 | name: 'tundra3', 250 | colors: [ 251 | '#87c3ca', 252 | '#7b7377', 253 | '#b2475d', 254 | '#7d3e3e', 255 | '#eb7f64', 256 | '#d9c67a', 257 | '#f3f2f2' 258 | ] 259 | }, 260 | { 261 | name: 'tundra4', 262 | colors: [ 263 | '#d53939', 264 | '#b6754d', 265 | '#a88d5f', 266 | '#524643', 267 | '#3c5a53', 268 | '#7d8c7c', 269 | '#dad6cd' 270 | ] 271 | }, 272 | { 273 | name: 'retro', 274 | colors: [ 275 | '#69766f', 276 | '#9ed6cb', 277 | '#f7e5cc', 278 | '#9d8f7f', 279 | '#936454', 280 | '#bf5c32', 281 | '#efad57' 282 | ] 283 | }, 284 | { 285 | name: 'retro-washedout', 286 | colors: [ 287 | '#878a87', 288 | '#cbdbc8', 289 | '#e8e0d4', 290 | '#b29e91', 291 | '#9f736c', 292 | '#b76254', 293 | '#dfa372' 294 | ] 295 | }, 296 | { 297 | name: 'roygbiv-warm', 298 | colors: [ 299 | '#705f84', 300 | '#687d99', 301 | '#6c843e', 302 | '#fc9a1a', 303 | '#dc383a', 304 | '#aa3a33', 305 | '#9c4257' 306 | ] 307 | }, 308 | { 309 | name: 'roygbiv-toned', 310 | colors: [ 311 | '#817c77', 312 | '#396c68', 313 | '#89e3b7', 314 | '#f59647', 315 | '#d63644', 316 | '#893f49', 317 | '#4d3240' 318 | ] 319 | }, 320 | { 321 | name: 'present-correct', 322 | colors: [ 323 | '#fd3741', 324 | '#fe4f11', 325 | '#ff6800', 326 | '#ffa61a', 327 | '#ffc219', 328 | '#ffd114', 329 | '#fcd82e', 330 | '#f4d730', 331 | '#ced562', 332 | '#8ac38f', 333 | '#79b7a0', 334 | '#72b5b1', 335 | '#5b9bae', 336 | '#6ba1b7', 337 | '#49619d', 338 | '#604791', 339 | '#721e7f', 340 | '#9b2b77', 341 | '#ab2562', 342 | '#ca2847' 343 | ] 344 | } 345 | ]; 346 | 347 | var palettes$1 = palettes.map(p => { 348 | p.size = p.colors.length; 349 | return p; 350 | }); 351 | 352 | function get(name) { 353 | return palettes$1.find(pal => pal.name == name); 354 | } 355 | 356 | // List utils 357 | 358 | function contains(xs, y, eq) { 359 | return xs.some(x => eq(x, y)); 360 | } 361 | 362 | function union(xs, ys, eq) { 363 | let zs = []; 364 | xs.forEach(x => zs.push(x)); 365 | ys.forEach(y => { 366 | if (!contains(xs, y, eq)) zs.push(y); 367 | }); 368 | return zs; 369 | } 370 | 371 | function flatten(xs, eq) { 372 | return xs.reduce((acc, val) => union(acc, val, eq), []); 373 | } 374 | 375 | function get_random_from(list) { 376 | return list[Math.floor(Math.random() * list.length)] 377 | } 378 | 379 | // ---- Direction utils ---- 380 | 381 | // Get random direction among n choices. 382 | function random_dir(n) { 383 | if (n === 2) return Math.random() > 0.5 ? 0 : 2; // Up or down (50/50) 384 | if (n === 3) return Math.random() > 0.5 ? 1 : Math.random() > 0.5 ? 2 : 0; // Up, right or down (25/50/25) 385 | return Math.floor(Math.random() * n); // Up, right, down or left (25/25/25/25) 386 | } 387 | 388 | // Get horizontally mirrored direction. 389 | function mirror(dir) { 390 | if (is_vertical(dir)) return dir; 391 | if (dir == 1) return 3; 392 | if (dir == 3) return 1; 393 | } 394 | 395 | // Check wether direction is vertical. 396 | function is_vertical(dir) { 397 | return dir == 0 || dir == 2; 398 | } 399 | 400 | const sketch = p => { 401 | let app_gen, apparata; 402 | const number_of_apparata = 9; 403 | const scale = 2.5; 404 | const shuffle = 300; 405 | let tick = 0; 406 | const final_frame_duration = 120; 407 | let symmetric_assembly = true; 408 | const movement_length = 0.85; 409 | const row_length = 3; 410 | 411 | p.setup = () => { 412 | p.createCanvas(1200, 1000); 413 | p.background('#eee8e2'); 414 | p.fill(0); 415 | p.frameRate(40); 416 | p.strokeWeight(1.5); 417 | p.stroke('#5c3936'); 418 | 419 | apparata = []; 420 | app_gen = new index(25, 35, { 421 | solidness: 0.5, 422 | initiate_chance: 0.9, 423 | extension_chance: 0.88, 424 | vertical_chance: 0.6, 425 | roundness: 0, 426 | group_size: 0.82, 427 | colors: get('retro-washedout').colors 428 | }); 429 | 430 | for (let i = 0; i < number_of_apparata / row_length; i++) { 431 | setup_apparatus(row_length); 432 | } 433 | }; 434 | 435 | function setup_apparatus(n) { 436 | let apparatus = app_gen.generate(); 437 | 438 | for (let i = 0; i < n; i++) { 439 | let extra_shuffle = p.floor(p.random(100)); 440 | let animatable_app = populate_apparatus(apparatus, extra_shuffle); 441 | animate_apparatus(animatable_app, extra_shuffle); 442 | apparata.push(animatable_app); 443 | } 444 | } 445 | 446 | function populate_apparatus(app, extra_shuffle) { 447 | let new_app = []; 448 | app.forEach(part => { 449 | let new_part = { 450 | ...part, 451 | x2: part.x1 + part.w, 452 | y2: part.y1 + part.h, 453 | path: [] 454 | }; 455 | for (let i = 0; i < final_frame_duration - extra_shuffle; i++) { 456 | new_part.path.push({ x: new_part.x1, y: new_part.y1 }); 457 | } 458 | new_app.push(new_part); 459 | }); 460 | return new_app; 461 | } 462 | 463 | function animate_apparatus(apparatus, extra_shuffle) { 464 | symmetric_assembly = true; 465 | let chosen, origin, direction; 466 | let start_from_new_part = true; 467 | let actual_ff_duration = final_frame_duration - extra_shuffle; 468 | for (let i = actual_ff_duration; i < shuffle; i++) { 469 | if (i - actual_ff_duration >= (shuffle - actual_ff_duration) / 2) { 470 | symmetric_assembly = false; 471 | } 472 | 473 | apparatus.forEach(part => { 474 | part.path.push({ x: part.x1, y: part.y1 }); 475 | }); 476 | if (start_from_new_part) { 477 | chosen = get_random_from(apparatus); 478 | origin = symmetric_assembly 479 | ? get_with_id(apparatus, chosen.id) 480 | : [chosen]; 481 | direction = 482 | symmetric_assembly && origin.length === 1 483 | ? random_dir(2) 484 | : random_dir(symmetric_assembly ? 3 : 4); 485 | } 486 | start_from_new_part = p.random() > movement_length; 487 | 488 | if (is_vertical(direction) || !symmetric_assembly) { 489 | let neighborhood = get_neighborhood(origin, apparatus, direction); 490 | shift_all(neighborhood, direction, i); 491 | } else { 492 | let neighborhood_left = get_neighborhood( 493 | [origin[0]], 494 | apparatus, 495 | mirror(direction) 496 | ); 497 | let neighborhood_right = get_neighborhood( 498 | [origin[1]], 499 | apparatus, 500 | direction 501 | ); 502 | shift_all(neighborhood_left, mirror(direction), i); 503 | shift_all(neighborhood_right, direction, i); 504 | } 505 | } 506 | } 507 | 508 | p.draw = () => { 509 | p.background('#eee8e2'); 510 | p.translate( 511 | (p.width - (app_gen.xdim + 2) * scale) / (row_length + 1), 512 | (p.height - (app_gen.ydim + 2) * scale) / 6 513 | ); 514 | 515 | if (tick >= shuffle) { 516 | apparata = []; 517 | for (let i = 0; i < number_of_apparata / row_length; i++) { 518 | setup_apparatus(row_length); 519 | } 520 | tick = 0; 521 | } 522 | 523 | for (let i = 0; i < apparata.length; i++) { 524 | display_apparatus(apparata[i]); 525 | p.translate((p.width - (app_gen.xdim + 2) * scale) / (row_length + 1), 0); 526 | if (i % row_length === row_length - 1) 527 | p.translate( 528 | (-row_length * (p.width - (app_gen.xdim + 2) * scale)) / 529 | (row_length + 1), 530 | (p.height - (app_gen.ydim + 2) * scale) / 3 531 | ); 532 | } 533 | 534 | tick++; 535 | }; 536 | 537 | function display_apparatus(appar) { 538 | appar.forEach(part => { 539 | display_rect(part, scale, shuffle - tick - 1); 540 | }); 541 | } 542 | 543 | function get_neighborhood(ps, rs, dir) { 544 | let ns = ps; 545 | let ms = union( 546 | ns, 547 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 548 | equal_rect 549 | ); 550 | 551 | while (ms.length > ns.length) { 552 | ns = ms; 553 | ms = union( 554 | ns, 555 | flatten(ns.map(n => get_neighbors(n, rs, dir)), equal_rect), 556 | equal_rect 557 | ); 558 | } 559 | return ms; 560 | } 561 | 562 | function get_neighbors(r1, rs, dir) { 563 | return rs.filter(r => is_neighbor(r1, r, dir)); 564 | } 565 | 566 | function is_neighbor(r1, r2, dir) { 567 | if (equal_rect(r1, r2)) return false; // Identical 568 | if (dir == 0) return r2.y2 == r1.y1 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // North 569 | if (dir == 1) return r2.x1 == r1.x2 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // East 570 | if (dir == 2) return r2.y1 == r1.y2 && r2.x1 < r1.x2 && r2.x2 > r1.x1; // South 571 | if (dir == 3) return r2.x2 == r1.x1 && r2.y1 < r1.y2 && r2.y2 > r1.y1; // West 572 | return false; // Error 573 | } 574 | 575 | function equal_rect(r1, r2) { 576 | return r1.x1 == r2.x1 && r1.y1 == r2.y1 && r1.x2 == r2.x2 && r1.y2 == r2.y2; 577 | } 578 | 579 | function shift_all(rs, dir, time) { 580 | rs.forEach(r => shift(r, dir, time)); 581 | } 582 | 583 | function shift(r, dir, time) { 584 | let sx = is_vertical(dir) ? 0 : dir == 1 ? 1 : -1; 585 | let sy = !is_vertical(dir) ? 0 : dir == 2 ? 1 : -1; 586 | 587 | r.x1 += sx; 588 | r.y1 += sy; 589 | r.x2 += sx; 590 | r.y2 += sy; 591 | r.path[time] = { x: r.x1, y: r.y1 }; 592 | } 593 | 594 | function get_with_id(rs, id) { 595 | return rs.filter(r => r.id === id); 596 | } 597 | 598 | function display_rect(r, scale, time) { 599 | p.fill(r.col ? r.col : '#fff'); 600 | p.rect( 601 | r.path[time].x * scale, 602 | r.path[time].y * scale, 603 | r.w * scale, 604 | r.h * scale 605 | ); 606 | } 607 | 608 | p.keyPressed = () => { 609 | if (p.keyCode === 83) symmetric_assembly = !symmetric_assembly; 610 | else if (p.keyCode === 80) p.saveCanvas('apparatus_assembly', 'png'); 611 | }; 612 | }; 613 | 614 | new p5(sketch); 615 | 616 | }))); 617 | --------------------------------------------------------------------------------